ScarletBlackMarket/script/player/default_player/lasers.dnh

323 lines
12 KiB
Plaintext

// Lasers that don't penetrate
// Can be pasted into default player Rumia by adding `TLasers();` to @Initialize
// Activation logic is focused-shooting with a slight delay, similar to CAVE games.
// "RemovePLaserGfx()" can go into shutting-down (probably not necessary).
// Made by razzy
task TLasers{
InitPLaserGfx(); // Probably effects that occur when the laser reaches an enemy? (KEV)
let lasers0 = [ID_INVALID,ID_INVALID,ID_INVALID,ID_INVALID,ID_INVALID,ID_INVALID,ID_INVALID,ID_INVALID,ID_INVALID];
// this delays when the laser fires
// laser fires when laserMode==0
// laserMode increments by 1 per frame
// so laser fires after 15 frames
// KEV: To fire the laser immediately, just set laserMode to 0.
let laserLimit = 15;
let laserMode=-laserLimit;
loop{
if(IsPermitPlayerShot
&& GetPlayerState!=STATE_HIT
&& GetPlayerState!=STATE_DOWN
&& GetPlayerState!=STATE_END
&& (GetVirtualKeyState(VK_SHOT)==KEY_PUSH || GetVirtualKeyState(VK_SHOT)==KEY_HOLD)
){
if(GetVirtualKeyState(VK_SLOWMOVE)==KEY_PUSH || GetVirtualKeyState(VK_SLOWMOVE)==KEY_HOLD){
laserMode=min(0,laserMode+1);
}
else{
// reset laser delay when slowmove is let go
laserMode=-laserLimit;
}
if(laserMode>=0){
let odd = 1;
//NonPenetrationLaser(obj, xoff, yoff, ang, spd, maxLen, dmg, IsStrongLaser, width, vwidth)
// KEV: Ascent loop determines the number of lasers that will be spawned.
ascent(i in 0..1){
let las = lasers0[i];
if(Obj_IsValueExists(las,"DEL") || Obj_IsDeleted(las)){
let magnitude = 1;
lasers0[i]=NonPenetrationLaser(objPlayer, 0, -36, 270, 40, GetStgFrameHeight()*1.5, 2, true, 2, 2);
}
odd=-odd;
}
//PlaySnd(SND_p_shot, 93);
}
}
else{
// reset laser delay when fire is let go
// KEV: Unnecessary if the laser fires immediately
laserMode=-laserLimit;
}
yield;
}
// of an array of 2d coords, return the one closest to (sx,sy). Also returns the distance from (sx,sy) as the third value.
function GetClosestCoord(coords, sx,sy){
let closest=[];
let last_dist=99999;
let arrayLen=length(coords);
if(arrayLen==1){
let cur_dist;
if(length(coords[0])==3){ cur_dist=coords[0][2]; }
else{ cur_dist=((coords[0][0]-sx)^2+(coords[0][1]-sy)^2)^0.5; }
closest=[coords[0][0],coords[0][1], cur_dist];
}
else{
ascent(i in 0..arrayLen){
let cur_dist;
if(length(coords[i])==3){ cur_dist=(coords[i][2]); }
else{ cur_dist=((coords[i][0]-sx)^2+(coords[i][1]-sy)^2)^0.5; }
if(cur_dist < last_dist){
last_dist=cur_dist;
closest=[coords[i][0], coords[i][1], cur_dist];
}
}
}
return closest;
}
// non-penetrating laser object
function NonPenetrationLaser(obj, xoff, yoff, ang, spd, maxLen, dmg, IsStrongLaser, width, vwidth){
/* KEV: Parameter explanations:
obj: Where the laser is fired from
xoff/yoff: x and y offsets relative to the object
ang, spd: Self-explanatory
maxLen: max length of the laser, DON'T SET THIS TOO HIGH! GetStgFrameHeight() + a generous number should be enough
dmg: Self-explanatory
IsStrongLaser: use this if you want to differentiate between strong and weak lasers I guess, I don't play CAVE games so idk
width, vwidth: intersection & render width of laser
*/
// KEV: Defines the laser object.
let damager = ObjShot_Create(OBJ_STRAIGHT_LASER);
ObjShot_SetGraphic(damager, 1);
Obj_SetVisible(damager, false);
ObjShot_Regist(damager);
ObjShot_SetDamage(damager, dmg);
ObjShot_SetPenetration(damager, 8);
ObjLaser_SetLength(damager, 0);
ObjLaser_SetIntersectionWidth(damager, 64*width);
ObjLaser_SetRenderWidth(damager, 64*vwidth);
ObjStLaser_SetAngle(damager, ang);
TNonPenetrationLaser();
return damager;
task TNonPenetrationLaser(){
let scroll=rand(0,64);
let len=0; // the length of the laser (increased until maxLen)
let cosine=cos(ang); // grab this value for placement of the tip. **this assumes the laser never changes angle**
let sine=sin(ang); // grab this value for placement of the tip. **this assumes the laser never changes angle**
// **for lasers that change angle, cosine and sine will need to be updated**
// Laser is now firing
while(!Obj_IsDeleted(damager) // KEV: The four following states can be reduced to !ripplayer in my player scripts
&& IsPermitPlayerShot
&& GetPlayerState!=STATE_HIT
&& GetPlayerState!=STATE_DOWN
&& GetPlayerState!=STATE_END // Reduce these below to != KEY_FREE
&& (GetVirtualKeyState(VK_SHOT)==KEY_PUSH || GetVirtualKeyState(VK_SHOT)==KEY_HOLD)
&& (GetVirtualKeyState(VK_SLOWMOVE)==KEY_PUSH || GetVirtualKeyState(VK_SLOWMOVE)==KEY_HOLD)
){
CheckLaserIntersection;
// Set the laser's position and length
ObjMove_SetPosition(damager, ObjRender_GetX(obj)+xoff,ObjRender_GetY(obj)+yoff);
len = min(maxLen, len+spd);
ObjLaser_SetLength(damager, len);
MakeLaserGfxFrame;
yield;
}
// This laser isn't deleted just yet. I let it fly away first.
// This value lets other tasks check when this happens.
Obj_SetValue(damager, "DEL", true);
// Laser is now leaving
ObjMove_SetAngle(damager, ObjStLaser_GetAngle(damager));
ObjMove_SetSpeed(damager, spd);
while(!Obj_IsDeleted(damager) && len>8){
MakeLaserGfxFrame;
len=max(0,len-spd);
ObjLaser_SetLength(damager, len);
CheckLaserIntersection;
yield;
}
Obj_Delete(damager);
function MakeLaserGfxFrame(){
let hyper=0;
let gLen = max(0, len-32-24);
let gLen2 = max(0, len-32);
// add sprites to the per-frame sprite list
// KEV: Function reference: PLaserGfxFrameLaser(gfxObj, u, v, u2, v2, ry, x, y, ang, sx, sy, rd, gn, bl, al)
/*
KEV: Function description:
Adds a vertex to the attached gfxObj sprite list. Uses u, v, u2, v2 as coordinates for the SourceRect, ry for DestRect (rx is calculated in the function), x & y for vertex position, sx & sy for the scaling, rd, gn and bl for colors, and al for alpha.
PLaserGfxFrame is similar but is used for the base and tip of the laser. The ry (and by extension rx) parameters are removed.
(The color values should be unnecessary.)
*/
// ObjStLaser_SetEndGraphic (ph3sx) may come in handy for the base/tips.
// !IsStrongLaser = 0, IsStrongLaser = 1
PLaserGfxFrameLaser(lasGfxObj[0], 32+hyper*32+!IsStrongLaser*64, scroll, 63+hyper*32+!IsStrongLaser*64, (len-1)/4+scroll, 64, ObjMove_GetX(damager)+cosine*24, ObjMove_GetY(damager)+sine*24, ang+90, vwidth*rand(0.75,1.0),gLen/64, 255, 255, 255, 255);
PLaserGfxFrameLaser(lasGfxObj[1], 32+hyper*32+!IsStrongLaser*64, scroll, 63+hyper*32+!IsStrongLaser*64, (len-1)/4+scroll, 64, ObjMove_GetX(damager)+cosine*24, ObjMove_GetY(damager)+sine*24, ang+90, vwidth*rand(0.75,1.0), gLen/64, 255, 255, 255, 48);
PLaserGfxFrame(lasGfxObj[2], 160, 0, 191, 31, ObjMove_GetX(damager)+cosine*24, ObjMove_GetY(damager)+sine*24, ang+90, vwidth+rand(0.4, 0.5), 6, 255,255,255, 255); // Base of laser
PLaserGfxFrame(lasGfxObj[2], 160, 32, 191, 63, ObjMove_GetX(damager)+cosine*gLen2, ObjMove_GetY(damager)+sine*gLen2, ang+90, vwidth+rand(0.4, 0.5), 6, 255, 255, 255, 255);// Tip of laser
scroll += 3; // Scrolls the laser's graphic.
}
function CheckLaserIntersection(){
// ------------------ Check for enemy collisions ------------------
let enemies=ObjCol_GetListOfIntersectedEnemyID(damager); // Get enemy array
let closest=[];
let arrayLen=length(enemies);
// Check all enemies hit (if any)
if(arrayLen>0){
let enm_pos=[]; // will be a 2-dimensional array
// KEV: If there is no strong/weak laser differentiation, this ascent loop can be removed entirely(?)
ascent(i in 0..arrayLen){ // go through the enemies list
// Weaker lasers get pushed back by "popcorn" enemies.
// KEV: I don't really want to implement strong/weak laser differentiations...
if(!IsStrongLaser || ObjEnemy_GetInfo(enemies[i], INFO_LIFE) > 1){
// There are multiple collisions per enemy to check as well
// It's rare that there's more than one, but it's allowed
let pos=GetEnemyIntersectionPositionByIdA1(enemies[i]); // KEV: Returns the multiple hitboxes of the enemy as a 2D array, format is [index][x, y of hitbox].
/* KEV: Further explanation (rough and probably incorrect);
An enemy has 2 hitboxes, one at coords [16, 16] and one at [32, 32].
You get a 2D array named "coords" containing the coordinates of these 2 hitboxes by using GetEnemyIntersectionPositionByIdA1.
You then write this line "float num = coords[0][1];" and WriteLog() the value of num.
coords[0][1] corresponds to the y coordinate of the first hitbox, which would give you a num value of 16.
(I don't know the order the hitboxes will be sorted in the array, though. For all I know, coords[0][1] may be the second hitbox's y (32)...?)
*/
let closest2=GetClosestCoord(pos, ObjMove_GetX(damager),ObjMove_GetY(damager));
if(closest2[0]!=-1234){
enm_pos=enm_pos~[closest2];
}
}
}
closest = GetClosestCoord(enm_pos, ObjMove_GetX(damager),ObjMove_GetY(damager));
}
// ------------------ ------------------ ------------------
// There has been a collision, dial back laser length to (roughly) the point of collision.
// (Roughly) because we can't get the exact location, and doing it ourselves requires finding the hitbox dimensions.
// KEV: Getting the hitbox dimensions/locations should be perfectly possible with ph3sx's intersection-obtaining functions. Will need re-examining
if(length(closest) > 0){
let dist=closest[2]-16;
len=max(0,dist);
}
}
}
} // NonPenetrationLaser
}
let lasGfxObj=[]; // KEV: An array that will contain three sprite lists, rendering three different parts of the laser (base, body, tip).
// KEV: Creates the sprite lists, assigns the texture to them, and adds them as indexes into lasGfxObj.
task InitPLaserGfx(){
let imgLaser = GetCurrentScriptDirectory() ~ "laser_fx.png";
LoadTexture(imgLaser);
ascent(i in 0..3){
let gfx = ObjPrim_Create(OBJ_SPRITE_LIST_2D);
ObjPrim_SetTexture(gfx, imgLaser);
Obj_SetRenderPriorityI(gfx, 41);
lasGfxObj = lasGfxObj~[gfx];
}
ObjRender_SetBlendType(lasGfxObj[1], BLEND_ADD_ARGB);
Obj_SetRenderPriorityI(lasGfxObj[2], 42);
while(true){
ascent(i in 0..length(lasGfxObj)){
ObjSpriteList2D_ClearVertexCount(lasGfxObj[i]);
}
yield;
}
}
task RemovePLaserGfx{
let imgLaser = GetCurrentScriptDirectory() ~ "laser_fx.png";
RemoveTexture(imgLaser);
ascent(i in 0..length(lasGfxObj)){
Obj_Delete(lasGfxObj[i]);
lasGfxObj[i]=ID_INVALID;
}
}
// See the non-penetrating laser object task for information on these tasks.
task PLaserGfxFrame(gfxObj, u, v, u2, v2, x, y, ang, sx, sy, rd, gn, bl, al){
if(gfxObj==ID_INVALID){return;}
ObjRender_SetPosition(gfxObj, x, y, 0);
ObjRender_SetAngleZ(gfxObj, ang);
ObjRender_SetScaleXYZ(gfxObj, sx, sy, 1);
ObjRender_SetColor(gfxObj, rd, gn, bl);
ObjRender_SetAlpha(gfxObj, al);
ObjSpriteList2D_SetSourceRect(gfxObj, u, v, u2, v2);
ObjSpriteList2D_SetDestCenter(gfxObj);
ObjSpriteList2D_AddVertex(gfxObj);
}
task PLaserGfxFrameLaser(gfxObj, u, v, u2, v2, ry, x, y, ang, sx, sy, rd, gn, bl, al){
if(gfxObj==ID_INVALID){return;}
ObjRender_SetPosition(gfxObj, x,y,0);
ObjRender_SetAngleZ(gfxObj, ang);
ObjRender_SetScaleXYZ(gfxObj, sx ,sy, 1);
ObjRender_SetColor(gfxObj, rd,gn,bl);
ObjRender_SetAlpha(gfxObj, al);
ObjSpriteList2D_SetSourceRect(gfxObj, u, v, u2, v2);
let rx=(u2-u)/2;
ObjSpriteList2D_SetDestRect(gfxObj, -rx,0,rx,-ry);
ObjSpriteList2D_AddVertex(gfxObj);
}