489 lines
16 KiB
Plaintext
489 lines
16 KiB
Plaintext
|
|
task _LaserSpriteRender(
|
|
img, int target, float targetAng,
|
|
int rectLeft, int rectTop, int rectRight, int rectBottom,
|
|
float scaleX, float scaleY, int renderPriority, float alpha,
|
|
float scaleSpeed
|
|
){
|
|
|
|
let lasersprite = ObjPrim_Create(OBJ_SPRITE_2D);
|
|
|
|
ObjPrim_SetTexture(lasersprite, img);
|
|
ObjSprite2D_SetSourceRect(lasersprite, rectLeft, rectTop, rectRight, rectBottom);
|
|
ObjRender_SetScaleXYZ(lasersprite, 0, scaleY, 1);
|
|
ObjSprite2D_SetDestRect(lasersprite, -(rectRight-rectLeft)/2, 0, (rectRight-rectLeft)/2, rectTop-rectBottom);
|
|
Obj_SetRenderPriorityI(lasersprite, renderPriority);
|
|
ObjRender_SetAlpha(lasersprite, 0);
|
|
ObjRender_SetBlendType(lasersprite, BLEND_ADD_ARGB);
|
|
|
|
async{
|
|
loop{
|
|
int[] EnemyList = ObjCol_GetListOfIntersectedEnemyID(target);
|
|
if(GetVirtualKeyState(VK_SHOT) != KEY_FREE && GetVirtualKeyState(VK_SLOWMOVE) != KEY_FREE && !ripplayer && IsPermitPlayerShot){
|
|
|
|
// Assumes the intersecting color is right next to the right of the original color
|
|
|
|
if (length(EnemyList) > 0) {ObjSprite2D_SetSourceRect(lasersprite, rectRight, rectTop, rectRight+(rectRight-rectLeft), rectBottom);}
|
|
else{ObjSprite2D_SetSourceRect(lasersprite, rectLeft, rectTop, rectRight, rectBottom);}
|
|
float targetx = ObjRender_GetX(target);
|
|
float targety = ObjRender_GetY(target);
|
|
ObjRender_SetScaleX(lasersprite, min(scaleX+rand(-0.025, 0.025), ObjRender_GetScaleX(lasersprite) + scaleSpeed));
|
|
ObjRender_SetAlpha(lasersprite, min(alpha, ObjRender_GetAlpha(lasersprite)+alpha/5));
|
|
ObjRender_SetAngleZ(lasersprite, targetAng);
|
|
ObjRender_SetPosition(lasersprite, targetx, targety, 1);
|
|
yield;
|
|
}
|
|
else{
|
|
ObjRender_SetScaleX(lasersprite, max(0, ObjRender_GetScaleX(lasersprite) - scaleSpeed));
|
|
ObjRender_SetAlpha(lasersprite, max(0, ObjRender_GetAlpha(lasersprite)-alpha/5));
|
|
yield;
|
|
}
|
|
yield;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
function _RenderLaser(target, float ang, float maxintersectX, float maxLength, float width, float speedLength, float dmg){ /* */
|
|
|
|
float angRender = asin(maxintersectX/absolute(maxLength));
|
|
//int laser = CreatePlayerShotA1(ObjRender_GetX(target), ObjRender_GetY(target), 0, ang, dmg, 9999999, 0);
|
|
int laser = CreateStraightLaserA1(ObjRender_GetX(target), ObjRender_GetY(target), ang-90, maxLength, width, 9999999, 0, 1);
|
|
ObjLaser_SetInvalidLength(laser, 0, 0);
|
|
ObjShot_SetDamage(laser, dmg);
|
|
ObjShot_SetAutoDelete(laser, false);
|
|
Obj_SetRenderPriorityI(laser, 38);
|
|
_Follow(laser, target);
|
|
|
|
_LaserSpriteRender(
|
|
teamimg, laser, ang,
|
|
3072, 0, 3072+256, 256,
|
|
0.45, 20, 38, 240,
|
|
0.14
|
|
);
|
|
|
|
// When shot key is being held, create a line intersection that stretches across the laser.
|
|
|
|
/*
|
|
Calculations:
|
|
|
|
startx: x of target
|
|
|
|
starty: y of target
|
|
|
|
endx: x of laser
|
|
|
|
endy: maxLength
|
|
|
|
width = width of laser
|
|
*/
|
|
async{
|
|
|
|
loop{
|
|
|
|
if(GetVirtualKeyState(VK_SHOT) != KEY_FREE && GetVirtualKeyState(VK_SLOWMOVE) != KEY_FREE && !ripplayer && IsPermitPlayerShot){
|
|
|
|
if(shotspeed % 5 == 0){ObjSound_Play(inferno);}
|
|
ObjShot_SetIntersectionEnable(laser, true);
|
|
//ObjShot_SetIntersectionLine(laser, ObjRender_GetX(target), ObjRender_GetY(target), ObjRender_GetX(target)+maxintersectX, -maxLength, width);
|
|
//WriteLog(ObjMove_GetX(laserfwd));
|
|
yield;
|
|
|
|
}
|
|
|
|
else{ObjShot_SetIntersectionEnable(laser, false); yield;}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// After shot key is released, let the laser leave and then delete it.
|
|
|
|
return laser;
|
|
}
|
|
|
|
|
|
task _Follow(follower, followed){
|
|
|
|
while(!Obj_IsDeleted(follower)){
|
|
float x = ObjRender_GetX(followed);
|
|
float y = ObjRender_GetY(followed);
|
|
ObjMove_SetPosition(follower, x, y);
|
|
yield;
|
|
}
|
|
|
|
}
|
|
|
|
// Special functions for Tenshi's CAVE laser shottype.
|
|
// Original code by Razzy/Razzly, edited and tweaked by Kevinmonitor
|
|
|
|
// DEBUG
|
|
|
|
float LaserAlpha = 0; // Not used
|
|
|
|
task _CAVELaser(){
|
|
|
|
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/laserLimit to 0.
|
|
|
|
let laserLimit = 0;
|
|
let laserMode=-laserLimit;
|
|
|
|
loop{
|
|
if(
|
|
GetPlayerState!=STATE_HIT
|
|
&& GetPlayerState!=STATE_DOWN
|
|
&& GetPlayerState!=STATE_END
|
|
&& (GetVirtualKeyState(VK_SHOT)==KEY_PUSH || GetVirtualKeyState(VK_SHOT)==KEY_HOLD)
|
|
){
|
|
|
|
if(IsPlayerSpellActive()){
|
|
laserMode=min(0,laserMode+1);
|
|
}
|
|
|
|
else{
|
|
// reset laser delay when slowmove is let go
|
|
laserMode=-laserLimit;
|
|
}
|
|
|
|
laserMode=min(0,laserMode+1);
|
|
|
|
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, 7, 1.25, 1);
|
|
}
|
|
odd=-odd;
|
|
}
|
|
//PlaySnd(SND_p_shot, 93);
|
|
}
|
|
|
|
else{
|
|
// reset laser delay when fire is let go
|
|
// KEV: Unnecessary if the laser fires immediately
|
|
RemovePLaserGfx();
|
|
LaserAlpha = 0;
|
|
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, 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 (REMOVED HERE): 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.
|
|
|
|
int a = 0;
|
|
|
|
let damager = ObjShot_Create(OBJ_STRAIGHT_LASER);
|
|
|
|
ObjShot_SetGraphic(damager, 0);
|
|
Obj_SetVisible(damager, true);
|
|
ObjShot_Regist(damager);
|
|
ObjShot_SetDamage(damager, dmg);
|
|
ObjShot_SetPenetration(damager, 9999);
|
|
ObjShot_SetEraseShot(damager, true);
|
|
ObjLaser_SetLength(damager, 0);
|
|
ObjLaser_SetIntersectionWidth(damager, 256*width);
|
|
ObjLaser_SetRenderWidth(damager, 256*vwidth);
|
|
ObjStLaser_SetAngle(damager, ang); ObjShot_SetAutoDelete(damager, false);
|
|
ObjShot_SetIntersectionEnable(damager, true);
|
|
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(
|
|
IsPlayerSpellActive()
|
|
&& !Obj_IsDeleted(damager) // KEV: The four following states can be reduced to !ripplayer in my player scripts
|
|
&& !ripplayer
|
|
&& (GetVirtualKeyState(VK_SHOT) != KEY_FREE)
|
|
){
|
|
|
|
a++;
|
|
if(a % 5 == 0){ObjSound_Play(inferno);}
|
|
CheckLaserIntersection;
|
|
// Set the laser's position and length
|
|
ObjMove_SetPosition(damager, ObjMove_GetX(obj)+xoff,ObjMove_GetY(obj)+yoff);
|
|
len = min(maxLen, len+spd);
|
|
ObjLaser_SetLength(damager, len);
|
|
//ObjShot_SetIntersectionLine(damager, ObjMove_GetX(obj), ObjMove_GetY(obj)+yoff, ObjMove_GetX(obj), -len, 128*width);
|
|
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 > 128){
|
|
MakeLaserGfxFrame;
|
|
len=max(0,len-spd);
|
|
ObjLaser_SetLength(damager, len);
|
|
CheckLaserIntersection;
|
|
yield;
|
|
}
|
|
Obj_Delete(damager);
|
|
|
|
function MakeLaserGfxFrame(){
|
|
//let hyper=0;
|
|
|
|
// Values related to graphic lengths
|
|
|
|
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.
|
|
|
|
//(rd, gn, bl values have been removed)
|
|
|
|
// ObjStLaser_SetEndGraphic (ph3sx) may come in handy for the base/tips.
|
|
|
|
// After reaching the bottom rect, value loops back to the top.
|
|
|
|
PLaserGfxFrameLaser(lasGfxObj[0], 512, scroll, 1024, scroll*512, 64, ObjMove_GetX(damager)+cosine*24, ObjMove_GetY(damager)+sine*24, ang+90, vwidth*rand(1.0, 1.05), gLen/64, 255);
|
|
|
|
PLaserGfxFrameLaser(lasGfxObj[1], 512, scroll, 1024, scroll*512, 64, ObjMove_GetX(damager)+cosine*24, ObjMove_GetY(damager)+sine*24, ang+90, vwidth*rand(1.4, 1.5), gLen/64, 60); // Glow effect
|
|
|
|
PLaserGfxFrame(lasGfxObj[2], 512, 768, 1024, 1280, ObjMove_GetX(damager)+cosine*24, ObjMove_GetY(damager)+sine*24-20, ang+90, 1.15*vwidth+rand(0.2, 0.3), 1.15+rand(0.2, 0.3), 255); // Base of laser
|
|
|
|
PLaserGfxFrame(lasGfxObj[3], 1024, 0, 1536, 512, ObjMove_GetX(damager)+cosine*gLen2, 10+ObjMove_GetY(damager)+sine*gLen2, ang+90, 0.6*width+rand(0.2, 0.3), 0.8, 255);// Tip of laser
|
|
|
|
ObjRender_SetAngleZ(lasGfxObj[2], ObjRender_GetAngleZ(lasGfxObj[2])+10);
|
|
//WriteLog([ObjMove_GetX(damager), ObjMove_GetY(damager)]);
|
|
|
|
scroll += 0.05; // Scrolls the laser's graphic.
|
|
if (scroll >= 1) {scroll = 0.1;}
|
|
|
|
}
|
|
|
|
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(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.
|
|
|
|
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
|
|
|
|
// Set the damage of the laser.
|
|
|
|
float dmgBase = 12;
|
|
float dmgMax = 12;
|
|
|
|
if(length(closest) > 0){
|
|
let dist=closest[2]-16;
|
|
len=max(0,dist);
|
|
|
|
// The laser deals higher damage the closer it is to the enemy.
|
|
|
|
float ratioDist = 120/closest[2];
|
|
|
|
ObjShot_SetDamage(damager, Interpolate_Decelerate(dmgBase, dmgMax, min(1, ratioDist)));
|
|
|
|
// The laser changes colours depending on distance.
|
|
|
|
}
|
|
}
|
|
}
|
|
} // 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() ~ "./playerlib/Chimata_Sheet.png";
|
|
|
|
LoadTextureEx(imgLaser, true, true); // Loaded in main script
|
|
|
|
ascent(i in 0..4){
|
|
let gfx = ObjPrim_Create(OBJ_SPRITE_LIST_2D);
|
|
ObjSpriteList2D_SetAutoClearVertexCount(gfx, true);
|
|
ObjPrim_SetTexture(gfx, imgLaser);
|
|
Obj_SetRenderPriorityI(gfx, 41);
|
|
lasGfxObj = lasGfxObj~[gfx];
|
|
//ObjRender_SetBlendType(lasGfxObj[i-1], BLEND_ADD_ARGB);
|
|
}
|
|
|
|
async{
|
|
while(length(lasGfxObj) >= 3){
|
|
ObjRender_SetAngleZ(lasGfxObj[2], ObjRender_GetAngleZ(lasGfxObj[2])+10);
|
|
yield;
|
|
}
|
|
}
|
|
|
|
//ObjRender_SetBlendType(lasGfxObj[2], BLEND_ADD_ARGB);
|
|
Obj_SetRenderPriorityI(lasGfxObj[2], 42);
|
|
|
|
}
|
|
|
|
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(
|
|
int gfxObj,
|
|
float rectLeft, float rectTop, float rectRight, float rectBottom,
|
|
float posX, float posY,
|
|
float ang,
|
|
float scaleX, float scaleY,
|
|
float alpha){
|
|
|
|
if(gfxObj==ID_INVALID){return;}
|
|
|
|
//ObjRender_SetBlendType(gfxObj, BLEND_ADD_ARGB);
|
|
ObjRender_SetPosition(gfxObj, posX, posY, 0);
|
|
ObjRender_SetAngleZ(gfxObj, ang);
|
|
ObjRender_SetScaleXYZ(gfxObj, scaleX, scaleY, 1);
|
|
ObjSpriteList2D_SetSourceRect(gfxObj, rectLeft, rectTop, rectRight, rectBottom);
|
|
ObjSpriteList2D_SetDestCenter(gfxObj);
|
|
ObjSpriteList2D_AddVertex(gfxObj);
|
|
ObjRender_SetAlpha(gfxObj, alpha);
|
|
|
|
}
|
|
|
|
task PLaserGfxFrameLaser(
|
|
int gfxObj,
|
|
float rectLeft, float rectTop, float rectRight, float rectBottom,
|
|
float destY,
|
|
float posX, float posY,
|
|
float ang,
|
|
float scaleX, float scaleY,
|
|
float alpha){
|
|
|
|
if(gfxObj==ID_INVALID){return;}
|
|
|
|
//ObjRender_SetBlendType(gfxObj, BLEND_ADD_ARGB);
|
|
ObjRender_SetPosition(gfxObj, posX, posY,0);
|
|
ObjRender_SetAngleZ(gfxObj, ang);
|
|
ObjRender_SetScaleXYZ(gfxObj, scaleX, scaleY, 1);
|
|
ObjRender_SetAlpha(gfxObj, alpha);
|
|
ObjSpriteList2D_SetSourceRect(gfxObj, rectLeft, rectTop, rectRight, rectBottom);
|
|
let destX = (rectRight - rectLeft)/2;
|
|
ObjSpriteList2D_SetDestRect(gfxObj, -destX, 0, destX, -destY);
|
|
ObjSpriteList2D_AddVertex(gfxObj);
|
|
|
|
} |