import "std.zh"
import "LinkMovement.zh"
namespace DarkSoulsStatus{
void UpdateDeathPenalty(){
//If you're using my death penalty script, uncomment this line
// DeathPenalty[_DP_HASDIED] = 1;
}
//Status bar drawn position
const int STATUSBARS_ON_LINK = 0; //0 - Top of screen, 1 - Above Link
//Status warning drawn positio
const int STATUSWARNINGS_ON_LINK = 1; //0 - Center Screen, 1 - Above Link, 2 - Invisible
const int FONT_STATUSWARNINGS = FONT_SHERWOOD;
//Misc colors
const int C_STATUSBAR_BG = 0x0F; //Meter background
const int C_STATUSBAR_FG = 0x01; //Meter outline
const int C_STATUSBAR_TEXTSHADOW = 0x0F; //Text outline and other black things
//Stats for modifying status. Damage values are percentage based if negative. Decay is a percentage lost every frame.
//Stats for poison status
const int POISON_TILE = 460;
const int POISON_DAMAGE = 1;
const int POISON_TICKFREQ = 60;
const int POISON_DURATION = 2400;
const int POISON_DECAY = 0.05;
const int POISON_COLOR1 = 0x51;
const int POISON_COLOR2 = 0x52;
//Stats for toxic status
const int TOXIC_TILE = 461;
const int TOXIC_DAMAGE = -5;
const int TOXIC_TICKFREQ = 60;
const int TOXIC_DURATION = 1200;
const int TOXIC_DECAY = 0.01;
const int TOXIC_COLOR1 = 0xA1;
const int TOXIC_COLOR2 = 0xA2;
//Stats for bleed status
const int BLEED_TILE = 462;
const int BLEED_DAMAGE = -40;
const int BLEED_DECAY = 0.01;
const int BLEED_COLOR1 = 0x85;
const int BLEED_COLOR2 = 0x81;
//Stats for frost status
const int FROST_TILE = 463;
const int FROST_DAMAGE = -10;
const int FROST_HPPENALTY = 1.5; //Multiplier for damage while inflicted
const int FROST_MPPENALTY = 1.5; //Multiplier for MP used while inflicted
const int FROST_DURATION = 1200;
const int FROST_DECAY = 0.05;
const int FROST_COLOR1 = 0x72;
const int FROST_COLOR2 = 0x73;
//Stats for curse status
const int CURSE_TILE = 464;
const int CURSE_HPREDUCE = 0.5; //Percentage of HP reduced while cursed
const int CURSE_KILLS = 1; //Whether or not curse is instant death when inflicted
const int CURSE_DURATION = -1; //Duration of curse, -1 for infinite
const int CURSE_DECAY = 0.05;
const int CURSE_COLOR1 = 0xB1;
const int CURSE_COLOR2 = 0xB2;
//Sounds when inflicted with each status
const int SFX_POISON = 67;
const int SFX_TOXIC = 68;
const int SFX_BLEED = 69;
const int SFX_FROST = 70;
const int SFX_CURSE = 71;
//Sounds for damage ticks from statuses
const int SFX_POISON_TICK = 67;
const int SFX_TOXIC_TICK = 68;
int DSStatus[512];
//INTERNAL CONSTANTS, DON'T MESS WITH THESE
enum StatusType {POISON, TOXIC, BLEED, FROST, CURSE, NUMSTATUS};
const int _ANIM = 0;
const int _CURSEFRAME = 1;
const int _SWAMPCOUNTER = 2;
const int _STATUSDATASTART = 16;
enum StatusData {BUILDUP, TIME, MAXTIME, DECAYDELAY, NUMSTATUSDATA};
//END OF INTERNAL CONSTANTS
//Functions for accessing array indices
int GetData(int status, int which){
return DSStatus[_STATUSDATASTART+NUMSTATUSDATA*status+which];
}
void SetData(int status, int which, int val){
DSStatus[_STATUSDATASTART+NUMSTATUSDATA*status+which] = val;
}
void AddData(int status, int which, int val){
DSStatus[_STATUSDATASTART+NUMSTATUSDATA*status+which] += val;
}
void ClampData(int status, int which, int min, int max){
DSStatus[_STATUSDATASTART+NUMSTATUSDATA*status+which] = Clamp(DSStatus[_STATUSDATASTART+NUMSTATUSDATA*status+which], min, max);
}
//Functions for getting status settings
float StatusTile(int status){
switch(status){
case POISON:
return POISON_TILE;
case TOXIC:
return TOXIC_TILE;
case BLEED:
return BLEED_TILE;
case FROST:
return FROST_TILE;
case CURSE:
return CURSE_TILE;
}
}
float StatusDuration(int status){
switch(status){
case POISON:
return POISON_DURATION;
case TOXIC:
return TOXIC_DURATION;
case BLEED:
return 0;
case FROST:
return FROST_DURATION;
case CURSE:
return CURSE_DURATION;
}
}
float StatusDecay(int status){
switch(status){
case POISON:
return POISON_DECAY;
case TOXIC:
return TOXIC_DECAY;
case BLEED:
return BLEED_DECAY;
case FROST:
return FROST_DECAY;
case CURSE:
return CURSE_DECAY;
}
}
float StatusColor1(int status){
switch(status){
case POISON:
return POISON_COLOR1;
case TOXIC:
return TOXIC_COLOR1;
case BLEED:
return BLEED_COLOR1;
case FROST:
return FROST_COLOR1;
case CURSE:
return CURSE_COLOR1;
}
}
float StatusColor2(int status){
switch(status){
case POISON:
return POISON_COLOR2;
case TOXIC:
return TOXIC_COLOR2;
case BLEED:
return BLEED_COLOR2;
case FROST:
return FROST_COLOR2;
case CURSE:
return CURSE_COLOR2;
}
}
//Returns true if the status bars can be drawn
bool CanDrawStatus(){
switch(Link->Action){
case LA_SCROLLING:
case LA_HOLD1LAND:
case LA_HOLD2LAND:
case LA_HOLD1WATER:
case LA_HOLD2WATER:
return false;
}
return true;
}
//Draws a status meter to the screen
void DrawStatusMeter(int x, int y, int til, int c1, int c2, int percent, bool flash){
Screen->Rectangle(6, x+8, y+2, x+8+17, y+6, C_STATUSBAR_BG, 1, 0, 0, 0, true, 128);
Screen->Rectangle(6, x+8, y+2, x+8+17, y+6, C_STATUSBAR_FG, 1, 0, 0, 0, false, 128);
int c = c1;
if(flash&&DSStatus[_ANIM]%4<2)
c = c2;
Screen->Rectangle(6, x+9, y+3, x+Lerp(8+1, 8+16, percent), y+5, c, 1, 0, 0, 0, true, 128);
Screen->FastTile(6, x, y, til, 0, 128);
}
//These items will reduce the effect of status buildup. If 0, no item has that effect
const int I_RING_POISONBITE = 0;
const int MULTIPLIER_POISONBITE = 0.5;
const int I_RING_TOXINBITE = 0;
const int MULTIPLIER_TOXINBITE = 0.5;
const int I_RING_BLOODBITE = 0;
const int MULTIPLIER_BLOODBITE = 0.5;
const int I_RING_FROSTBITE = 0;
const int MULTIPLIER_FROSTBITE = 0.5;
const int I_RING_CURSEBITE = 0;
const int MULTIPLIER_CURSEBITE = 0.5;
//Fills a status by a certain amount
void FillStatus(int status, int amount, int maxTime){
switch(status){
case POISON:
if(I_RING_POISONBITE){
if(Link->Item[I_RING_POISONBITE])
amount = Ceiling(amount*MULTIPLIER_POISONBITE);
}
break;
case TOXIC:
if(I_RING_TOXINBITE){
if(Link->Item[I_RING_TOXINBITE])
amount = Ceiling(amount*MULTIPLIER_TOXINBITE);
}
break;
case BLEED:
if(I_RING_BLOODBITE){
if(Link->Item[I_RING_BLOODBITE])
amount = Ceiling(amount*MULTIPLIER_BLOODBITE);
}
break;
case FROST:
if(I_RING_FROSTBITE){
if(Link->Item[I_RING_FROSTBITE])
amount = Ceiling(amount*MULTIPLIER_FROSTBITE);
}
break;
case CURSE:
if(I_RING_CURSEBITE){
if(Link->Item[I_RING_CURSEBITE])
amount = Ceiling(amount*MULTIPLIER_CURSEBITE);
}
break;
}
AddData(status, BUILDUP, amount);
SetData(status, MAXTIME, maxTime);
SetData(status, DECAYDELAY, 48);
ClampData(status, BUILDUP, 0, 100);
}
//Damages Link with status
void DamageLinkStatus(int damage){
if(damage<0){
damage = Ceiling(Link->MaxHP*(Abs(damage)/100));
}
Link->HP -= damage;
}
void Init(){
DSStatus[_CURSEFRAME] = 0;
for(int i=0; i<NUMSTATUS; ++i){
SetData(i, BUILDUP, 0);
if(i!=CURSE)
SetData(i, TIME, 0);
SetData(i, MAXTIME, 0);
SetData(i, DECAYDELAY, 0);
}
if(GetData(CURSE, TIME)!=0){
genericdata gd = Game->LoadGenericData(Game->GetGenericScript("CurseEffect"));
gd->Running = true;
}
}
void Update(){
DSStatus[_ANIM] = (DSStatus[_ANIM]+1)%360;
int numMeters;
for(int i=0; i<NUMSTATUS; ++i){
int drawMeterFill;
bool flashMeter;
//Status is active
if(GetData(i, TIME)!=0){
switch(i){
case POISON:
break;
}
if(GetData(i, TIME)>0){
drawMeterFill = Clamp(GetData(i, TIME)/GetData(i, MAXTIME), 0, 1);
flashMeter = true;
AddData(i, TIME, -1);
}
else if(GetData(i, TIME)==-1){
drawMeterFill = 1;
}
SetData(i, BUILDUP, 0);
}
//Status is being filled
else if(GetData(i, BUILDUP)){
//Status is fully filled
if(GetData(i, BUILDUP)>=100){
SetData(i, BUILDUP, 0);
SetData(i, TIME, GetData(i, MAXTIME));
switch(i){
case POISON:
genericdata gd = Game->LoadGenericData(Game->GetGenericScript("PoisonEffect"));
gd->Running = true;
break;
case TOXIC:
genericdata gd = Game->LoadGenericData(Game->GetGenericScript("ToxicEffect"));
gd->Running = true;
break;
case BLEED:
genericdata gd = Game->LoadGenericData(Game->GetGenericScript("BleedEffect"));
gd->Running = true;
break;
case FROST:
genericdata gd = Game->LoadGenericData(Game->GetGenericScript("FrostEffect"));
gd->Running = true;
break;
case CURSE:
DSStatus[_CURSEFRAME] = 1;
genericdata gd = Game->LoadGenericData(Game->GetGenericScript("CurseEffect"));
gd->Running = true;
break;
}
}
//Status is filling/decaying
else{
if(GetData(i, DECAYDELAY)>0)
AddData(i, DECAYDELAY, -1);
else{
AddData(i, BUILDUP, -StatusDecay(i));
ClampData(i, BUILDUP, 0, 100);
}
drawMeterFill = Clamp(GetData(i, BUILDUP)/100, 0, 1);
}
}
if(drawMeterFill>0){
if(CanDrawStatus()){
if(STATUSBARS_ON_LINK)
DrawStatusMeter(Link->X-6, Link->Y-10-numMeters*10, StatusTile(i), StatusColor1(i), StatusColor2(i), drawMeterFill, flashMeter);
else
DrawStatusMeter(8+32*numMeters, 8, StatusTile(i), StatusColor1(i), StatusColor2(i), drawMeterFill, flashMeter);
}
++numMeters;
}
}
}
void DrawEffectLabel(int statusType, int str, int labelFrames, int drawTime){
if(STATUSWARNINGS_ON_LINK==2)
return;
int x = 128;
int y = 88;
if(STATUSWARNINGS_ON_LINK){
x = Link->X;
y = Link->Y-8;
int stringW = Text->StringWidth(str, FONT_STATUSWARNINGS);
if(x-stringW/2<8)
x += Abs((x-stringW/2)-8);
if(x+stringW/2>248)
x -= Abs((x+stringW/2)-248);
}
if(labelFrames[0]<drawTime){
int op = 128;
if(labelFrames[0]>=drawTime-8)
op = 64;
int c = StatusColor1(statusType);
if(labelFrames[0]%4<2)
c = StatusColor2(statusType);
Screen->DrawString(6, x, y-labelFrames[0]/2-Text->FontHeight(FONT_STATUSWARNINGS)/2, FONT_STATUSWARNINGS, c, -1, TF_CENTERED, str, op, SHD_OUTLINED8, C_STATUSBAR_TEXTSHADOW);
++labelFrames[0];
}
}
generic script PoisonEffect{
void run(){
this->ExitState[GENSCR_ST_RELOAD] = true;
this->ExitState[GENSCR_ST_CONTINUE] = true;
int labelFrames[1];
int damageTimer;
if(SFX_POISON)
Game->PlaySound(SFX_POISON);
while(GetData(POISON, TIME)>0){
WaitTo(SCR_TIMING_POST_GLOBAL_ACTIVE, false);
DrawEffectLabel(POISON, "POISONED", labelFrames, 64);
++damageTimer;
if(damageTimer>=POISON_TICKFREQ){
if(SFX_POISON_TICK)
Game->PlaySound(SFX_POISON_TICK);
DamageLinkStatus(POISON_DAMAGE);
damageTimer = 0;
}
Waitframe();
}
}
}
generic script ToxicEffect{
void run(){
this->ExitState[GENSCR_ST_RELOAD] = true;
this->ExitState[GENSCR_ST_CONTINUE] = true;
int labelFrames[1];
int damageTimer;
if(SFX_TOXIC)
Game->PlaySound(SFX_TOXIC);
while(GetData(TOXIC, TIME)>0){
WaitTo(SCR_TIMING_POST_GLOBAL_ACTIVE, false);
DrawEffectLabel(TOXIC, "TOXIC", labelFrames, 64);
++damageTimer;
if(damageTimer>=TOXIC_TICKFREQ){
if(SFX_TOXIC_TICK)
Game->PlaySound(SFX_TOXIC_TICK);
DamageLinkStatus(TOXIC_DAMAGE);
damageTimer = 0;
}
Waitframe();
}
}
}
const int SPR_BLEEDPARTICLES = 102; //2x2 sprite animation used for bleed
generic script BleedEffect{
void run(){
this->ExitState[GENSCR_ST_RELOAD] = true;
this->ExitState[GENSCR_ST_CONTINUE] = true;
int labelFrames[1];
int damageTimer;
if(SFX_BLEED)
Game->PlaySound(SFX_BLEED);
if(SPR_BLEEDPARTICLES){
lweapon bleed = CreateLWeaponAt(LW_SPARKLE, Link->X, Link->Y);
bleed->DrawXOffset = -8;
bleed->DrawYOffset = -8;
bleed->Extend = 3;
bleed->TileWidth = 2;
bleed->TileHeight = 2;
bleed->UseSprite(SPR_BLEEDPARTICLES);
}
int totalDamage = BLEED_DAMAGE;
if(BLEED_DAMAGE<0)
totalDamage = Ceiling(Link->MaxHP*(Abs(BLEED_DAMAGE)/100));
int remainingDamage = totalDamage;
for(int i=0; i<64; ++i){
WaitTo(SCR_TIMING_POST_GLOBAL_ACTIVE, false);
DrawEffectLabel(BLEED, "BLOOD LOSS", labelFrames, 64);
int tickDamage = Clamp(Ceiling(totalDamage/32), 0, remainingDamage);
Link->HP -= tickDamage;
remainingDamage -= tickDamage;
Waitframe();
}
}
}
generic script FrostEffect{
void run(){
this->ExitState[GENSCR_ST_RELOAD] = true;
this->ExitState[GENSCR_ST_CONTINUE] = true;
int labelFrames[1];
int damageTimer;
if(SFX_FROST)
Game->PlaySound(SFX_FROST);
int totalDamage = FROST_DAMAGE;
if(FROST_DAMAGE<0)
totalDamage = Ceiling(Link->MaxHP*(Abs(FROST_DAMAGE)/100));
int remainingDamage = totalDamage;
int lastLinkHP = Link->HP;
int lastLinkMP = Link->MP;
while(GetData(FROST, TIME)>0){
WaitTo(SCR_TIMING_POST_GLOBAL_ACTIVE, false);
DrawEffectLabel(FROST, "FROSTBITTEN", labelFrames, 64);
//Deal initial ticks of damage
if(remainingDamage>0){
int tickDamage = Clamp(Ceiling(totalDamage/32), 0, remainingDamage);
Link->HP -= tickDamage;
remainingDamage -= tickDamage;
}
//Extra damage when Link gets hit
else{
if(Link->HP<lastLinkHP){
int damage = lastLinkHP-Link->HP;
Link->HP -= Ceiling(damage*(FROST_HPPENALTY-1));
}
}
//Increase MP costs
if(Link->MP<lastLinkMP){
int cost = lastLinkMP-Link->MP;
Link->MP = Max(Link->MP-Ceiling(cost*(FROST_MPPENALTY-1)), 0);
}
lastLinkHP = Link->HP;
lastLinkMP = Link->MP;
Waitframe();
}
}
}
generic script CurseEffect{
void run(){
this->ExitState[GENSCR_ST_RELOAD] = true;
this->ExitState[GENSCR_ST_CONTINUE] = true;
int labelFrames[1];
int damageTimer;
if(DSStatus[_CURSEFRAME]){
if(SFX_CURSE)
Game->PlaySound(SFX_CURSE);
if(CURSE_KILLS){
genericdata gd = Game->LoadGenericData(Game->GetGenericScript("CurseDeathAnim"));
Link->Invisible = true;
Waitframe();
gd->RunFrozen();
}
}
DSStatus[_CURSEFRAME] = 0;
while(GetData(CURSE, TIME)!=0){
if(!CURSE_KILLS)
DrawEffectLabel(CURSE, "CURSED", labelFrames, 64);
if(Link->HP>Ceiling(Link->MaxHP*CURSE_HPREDUCE))
--Link->HP;
Waitframe();
}
}
}
//Data for the tile animation that plays when Link is cursed
const int CURSE_ANIM_TILE = 440;
const int CURSE_ANIM_W = 1;
const int CURSE_ANIM_H = 1;
const int CURSE_ANIM_FRAMES = 20;
const int CURSE_ANIM_ASPEED = 16;
generic script CurseDeathAnim{
void run(){
Game->PlayMIDI(0);
DSStatus[_CURSEFRAME] = 0;
int labelFrames[1];
int animTime = CURSE_ANIM_FRAMES*CURSE_ANIM_ASPEED;
int fadeSpeed = animTime/4;
if(animTime<16)
fadeSpeed = 0;
int xOff = (CURSE_ANIM_W-1)*-8;
int yOff = (CURSE_ANIM_H-1)*-8;
for(int i=0; i<animTime; ++i){
if(fadeSpeed){
if(i>fadeSpeed)
Screen->Rectangle(6, 0, -56, 255, 175, C_STATUSBAR_TEXTSHADOW, 1, 0, 0, 0, true, 128);
if(i>fadeSpeed/3*2)
Screen->Rectangle(6, 0, -56, 255, 175, C_STATUSBAR_TEXTSHADOW, 1, 0, 0, 0, true, 64);
if(i>fadeSpeed/3)
Screen->Rectangle(6, 0, -56, 255, 175, C_STATUSBAR_TEXTSHADOW, 1, 0, 0, 0, true, 64);
}
else{
Screen->Rectangle(6, 0, -56, 255, 175, C_STATUSBAR_TEXTSHADOW, 1, 0, 0, 0, true, 128);
}
Screen->DrawTile(6, Link->X+Link->DrawXOffset+xOff, Link->Y+Link->DrawYOffset+yOff, CURSE_ANIM_TILE+Floor(i/CURSE_ANIM_ASPEED), CURSE_ANIM_W, CURSE_ANIM_H, 6, -1, -1, 0, 0, 0, 0, true, 128);
if(fadeSpeed){
if(i>animTime-fadeSpeed/3)
Screen->Rectangle(6, 0, -56, 255, 175, C_STATUSBAR_TEXTSHADOW, 1, 0, 0, 0, true, 128);
if(i>animTime-fadeSpeed/3*2)
Screen->Rectangle(6, 0, -56, 255, 175, C_STATUSBAR_TEXTSHADOW, 1, 0, 0, 0, true, 64);
if(i>animTime-fadeSpeed)
Screen->Rectangle(6, 0, -56, 255, 175, C_STATUSBAR_TEXTSHADOW, 1, 0, 0, 0, true, 64);
}
Waitframe();
}
if(STATUSWARNINGS_ON_LINK<2){
for(int i=0; i<80; ++i){
Screen->Rectangle(6, 0, -56, 255, 175, C_STATUSBAR_TEXTSHADOW, 1, 0, 0, 0, true, 128);
DrawEffectLabel(CURSE, "CURSED", labelFrames, 64);
Waitframe();
}
}
UpdateDeathPenalty();
Game->Continue();
}
}
//NPC script to be put on enemies to apply status
npc script EnemyStatusContact{
void run(int statusType, int amount, int duration){
if(duration==0)
duration = StatusDuration(statusType);
while(true){
untyped hitby = Link->HitBy[0]; //HIT_BY_NPC
if(hitby>0){
npc hitNPC = Screen->LoadNPC(hitby);
if(hitNPC->isValid()){
if(hitNPC==this){
FillStatus(statusType, amount, duration);
}
}
}
Waitframe();
}
}
}
//Weapon script to be put on enemies to apply status
eweapon script EWeaponStatusContact{
void run(int statusType, int amount, int duration){
if(duration==0)
duration = StatusDuration(statusType);
while(true){
untyped hitby = Link->HitBy[1]; //HIT_BY_EWEAPON
if(hitby>0){
eweapon hitEW = Screen->LoadEWeapon(hitby);
if(hitEW->isValid()){
if(hitEW==this){
FillStatus(statusType, amount, duration);
}
}
}
Waitframe();
}
}
}
const int SFX_CURSE_CURE = 25;
//Simple item script to remove curse when used
itemdata script CurseCure{
void run(){
if(GetData(CURSE, TIME)!=0){
Game->PlaySound(25);
SetData(CURSE, TIME, 0);
Link->Item[this->ID] = false;
}
}
}
//This item makes it so swamp combos don't slow you down
const int I_RUSTEDIRONRING = 0;
//Poison swamps that inflict status. Miyazaki would be proud
combodata script PoisonSwamp{
void run(){
int statusType = this->Attribytes[0];
int amount = this->Attribytes[1];
int freq = this->Attribytes[2];
int onlyWalk = this->Attribytes[3];
int walkSFX = this->Attribytes[4];
int duration = this->Attrishorts[0];
int stepMod = this->Attributes[0];
int walkCMB = this->Attributes[1];
if(duration==0)
duration = StatusDuration(statusType);
while(true){
int pos = ComboAt(Link->X+8, Link->Y+12);
if(pos==this->Pos){
if(walkCMB){
Screen->FastCombo(4, Link->X+Link->DrawXOffset, Link->Y+Link->DrawYOffset+2, walkCMB, Screen->ComboC[this->Pos], 128);
}
if(!(I_RUSTEDIRONRING&&Link->Item[I_RUSTEDIRONRING]))
LinkMovement_AddLinkSpeedBoost(stepMod);
if(onlyWalk==0||Link->Action==LA_WALKING)
++DSStatus[_SWAMPCOUNTER];
if(DSStatus[_SWAMPCOUNTER]>=freq){
if(statusType>=0)
FillStatus(statusType, amount, duration);
if(walkSFX&&Link->Action==LA_WALKING)
Game->PlaySound(walkSFX);
DSStatus[_SWAMPCOUNTER] = 0;
}
}
Waitframe();
}
}
}
}
global script Active{
void run(){
DarkSoulsStatus::Init();
LinkMovement_Init();
while(true){
DarkSoulsStatus::Update();
LinkMovement_Update1();
Waitdraw();
LinkMovement_Update2();
Waitframe();
}
}
}