Help with complicated script
I'm trying to make a DMOD with a turn-based battle system. The combat seems to work fine until I win the fight - then, it refuses to end. I can't quite figure out why.
I apologize in advance if my code is a nightmare. I try.
I haven't implemented commands other than "attack" yet, but as long as you pick that over and over, combat works as intended until you win. The sound plays, it says you won, and it awards the exp and gold, but the enemy then proceeds to attack as if the winning never happened. I tried moving kill_this_task back into the strike procedure after win() is called, but it didn't help.
I apologize in advance if my code is a nightmare. I try.
//Here it is, my turn-based battle system. It's heavily based on Dragon Warrior's. //Thanks to Ryan8bit for taking apart the assembly code and figuring out how DW's system works. void start { if (&arg1 == 1) { //Fighting The Slime &mystr = 5 &mydef = 2 &myhp = 3 &myexp = 1 &mygold = 2 } runaway() fight() } void fight { command: choice_start title_start Command? title_end "Attack" "Magic" "Item" "Run" choice_end if (&result == 1) { strike() } if (&result == 2) { spell() } if (&result == 3) { herb() } if (&result == 4) { dinkrun() } enemy: //Enemy's turn say_stop_xy("`%The monster attacks!", 10, 370) //First, let's see how enemy's attack compares to Dink's defense. if (&mystr > &defense) { &jug = &mystr &jugb = &defense &jugb / 2 &jug -= &jugb &jug / 2 //&jug is now the top of enemy's attack range. &jugc = &jug &jugc / 2 //&jugc is now the bottom of enemy's attack range. &jug -= &jugc &jug += 1 //gotta add 1 to get the right range because of how random() works &jugb = random(&jug, &jugc) } else { &jug = &mystr &jug += 4 &jug / 6 &jug += 1 &jugb = random(&jug, 0) } if (&jugb > 0) { &life -= &jugb say_stop_xy("`%Dink takes &jugb damage.", 10, 370) } else { say_stop_xy("`%Dink is unharmed.", 10, 370) } if (&life > 0) { goto command; } else { //You lose. Let's make sure nothing else happens before the death cutscene wait(10000) } } void runaway { //let's give the enemy a chance to run away if Dink is too strong for them &jug = &strength &jug / 2 if (&jug > &mystr) { //Dink is over twice as strong as the monster, they should try to run &jugb = random(4, 1) if (&jugb == 4) { //successful run attempt by the monster say_stop_xy("`%The monster ran away!", 10, 370) kill_this_task } return } void strike { //Dink tries to hit the monster say_stop_xy("`%Dink attacks!", 10, 370) //First, there's a 1/64 chance the monster dodges &jug = random(64, 1) if (&jug == 64) { //tough luck, you missed say_stop_xy("`%But the monster dodges the attack!", 10, 370) goto enemy; } //Is the attack a critical hit? Let's find out. &jug = random(32, 1) if (&jug == 32) { //Crit! This bypasses the enemy's defense. &jug = &strength &jugb = &strength &jugb / 2 //&jug is top of attack's range, &jugb is bottom. So: &jug -= &jugb &jug += 1 //gotta add 1 to get the right range because of how random() works &dmg = random(&jug, &jugb) //&dmg is the value of Dink's attack. say_stop_xy("`%Critical hit!", 10, 370) goto pow; } //Now we determine the strength of Dink's attack if not a crit &jug = &strength &jug -= &mydef &jug / 2 //now &jug is half of (Dink's attack - Enemy defense). This is the TOP possible damage. if (&jug == 1) { //If top possible damage is 1, bottom needs to be 0. //Otherwise the range would be 1 to 1. &jugc = 0 goto calc; } &jugc = &jug &jugc / 2 //&jugc is a QUARTER of (Dink's attack - Enemy defense). This is the BOTTOM. &jug -= &jugc &jug += 1 &dmg = random(&jug, &jugc) if (&dmg <= 0) { //Uh oh, Dink's attack ain't doing squat. Let's give him one more chance to hit the thing. &jug = random(2, 1) if (&jug == 2) { //Dink wins the coinflip and damage becomes 1. &dmg = 1 } else { //Bummer, no damage. &dmg = 0 } } if (&dmg == 0) { say_stop_xy("`%The monster laughs at Dink's feeble attack.", 10, 370) goto enemy; } pow: say_stop_xy("`%Dink does &dmg damage.", 10, 370) &myhp -= &dmg if (&myhp <= 0) { //Monster is dead. win() kill_this_task } goto enemy; } void win { stopmidi playsound(56, 44100, 0, 0, 0) say_stop_xy("`%You have done well in defeating the monster.", 10, 370) &myexp * &expmulti &exp += &myexp &gold += &mygold say_stop_xy("`%You have gained &myexp experience points and &mygold gold.", 10, 370) kill_this_task }
I haven't implemented commands other than "attack" yet, but as long as you pick that over and over, combat works as intended until you win. The sound plays, it says you won, and it awards the exp and gold, but the enemy then proceeds to attack as if the winning never happened. I tried moving kill_this_task back into the strike procedure after win() is called, but it didn't help.
I think each command is treated as a seperate script, so kill_this_task() only ends win(), not fight(). Placing it in strike() isn't going to do anything for the same reason, you need to kill fight() rather than strike() or win().
At least that's what I'm guessing right now.
At least that's what I'm guessing right now.
Try adding a return at the end of win(). I know both kill_this_task and return seems redundant, but it's worked for me in the past; due reasons Meta explained, no doubt.
EDIT: Scratch that, return doesn't work any better.
EDIT: Scratch that, return doesn't work any better.
This is true. kill_this_task() kills a script instance. Whenever you blah(), that code is run in a new script instance. I think the if (&myhp < 0) block would do great just after handling the choices in void fight.
For a similar reason, I see that in void strike, you goto enemy. I suggest using the simple return, here's why:
Script instance 1 encounters the line "strike()", creates script instance 2, and waits for it to get back.
Script instance 2 searches for "void strike", finds it, runs it.
... encounters the line "goto enemy".
... searches for "enemy:", finds it, runs it.
... encounters "goto command".
... searches for "command:", finds it, runs it.
... encounters the line "strike()", creates instance 3, and waits for it to get back.
Script instance 3 searches for "void strike", finds it, runs it.
etc.
I expect an epic combat of lots of rounds to crash the game, because you'll run into the script limit. With return, it'll be script instance 1 that wakes up, hopefully skips the other &result checks, and proceeds at enemy:. I say "hopefully", because &result may change if whatever choice you made results in another choice menu.
For a similar reason, I see that in void strike, you goto enemy. I suggest using the simple return, here's why:
Script instance 1 encounters the line "strike()", creates script instance 2, and waits for it to get back.
Script instance 2 searches for "void strike", finds it, runs it.
... encounters the line "goto enemy".
... searches for "enemy:", finds it, runs it.
... encounters "goto command".
... searches for "command:", finds it, runs it.
... encounters the line "strike()", creates instance 3, and waits for it to get back.
Script instance 3 searches for "void strike", finds it, runs it.
etc.
I expect an epic combat of lots of rounds to crash the game, because you'll run into the script limit. With return, it'll be script instance 1 that wakes up, hopefully skips the other &result checks, and proceeds at enemy:. I say "hopefully", because &result may change if whatever choice you made results in another choice menu.
just make a new variable called &oldresult and just use &oldresult = &result. Then you just use &oldresult so that whatever menu might have popped up in the mean time is irrelevant. This is nice having to prevent some nasty bugs that are hard to replicate. I think the local variable &oldresult would also be unique to that script instance.
The Dink Engine just don't handle user-called procedures very well. It should be possible make this work right if you're very careful to kill them off after each use, but I think an easier solution would be (against all conventional programing advice) to rewrite it using only goto.
I rewrote it using only goto and it seems to have fixed the problem, thanks.