Twine / Sugarcube Encounter - Combat system.

DuskSarsis

Newbie
Apr 30, 2019
61
67
Hello everyone.~

I have started work on something of my own, but I am at a little roadblock. I can do almost everything I have in mind fairly easy with two exceptions that, as it happens, are pretty important! Now, I can code in python and C# and would be able to do this fairly easily, alas javascript and whatever twine/sugarcube uses... not so much.

I can handle combat since that's a pretty simple loop of attack/magic/flee decisions and immediate variable changes in response with random numbers for rolls. What I am struggling with is getting to the combat. I want to be able to put together a group of variables for each monster type you'll encounter, name, stats, so on, into a list for each area. When you enter a new page, it should do a random roll to decide if you trigger an encounter. If you do, then it should roll again from a table of the monsters in that area to decide what you fight, and then pass that monsters information to the combat script.
So, in order it needs to:
Roll encounter chance (10%)
If an encounter is triggered, roll on monster table
use the result on the monster table to get the variables for that monster which need to be grouped together somehow (how do you do tuples in this damn thing?!)
and finally pass them to a combat script which then uses those for the combat loop.

Ideally, which of several monster tables (14 in total) are used should be determined via a local variable that tells which area the player is in, so that the same code can be referenced for every dungeon without needing to be changed.

I could do some of this easily enough with tuples in python but have not yet seen a way to make tuples in sugarcube.
 

Alcahest

Engaged Member
Donor
Game Developer
Jul 28, 2017
3,625
4,486
You can use arrays and objects with javascript.

For data that doesn't need to be saved, you can use javascript variables. If you must save it, you can use the SugarCube $ variables for arrays and objects with the <<set>> macro.

You can also use the SugarCube setup object to easily use javascript variables from within SugarCube.
Code:
<<set setup.races = { troll : { str:20, dex:8 }, human : { str:12, dex:13} }>>
<<set _strdiff = setup.races.troll.str - setup.races.human.str>>
<<print _strdiff>> /* 8 */
 

DuskSarsis

Newbie
Apr 30, 2019
61
67
Yeee, thank you! I had just figured out the syntax to make objects work and get objects inside of an object (like player_character.attributes.strength to pull the strength value. I have constructed the first (easiest) monster for the starting dungeon.

<<set $monster_list={
slime:{
name: "Slime",
strength: 0,
dexterity: 0,
constitution: 0,
max_hp: 50,
current_hp:50,
gold:5,
item_list: ["rusty dagger", "rags", "stick"],
},

}>>

I will be adding 2 more for the first dungeon, but I have not yet determined how to make it randomly pick from those three monsters for just that dungeon, and a different set for a second dungeon.

Ideally I want to use the same code to do this, A-lah a function. Have the code pass the location to the function, so that the function then selects the monster list for that area, randomly picks one of those monsters, and passes the object of that monster (slime, in the above) back to begin combat against it.

Which, my unfamiliarity with this particular coding language is making hard.
 

Alcahest

Engaged Member
Donor
Game Developer
Jul 28, 2017
3,625
4,486
Yeee, thank you! I had just figured out the syntax to make objects work and get objects inside of an object (like player_character.attributes.strength to pull the strength value. I have constructed the first (easiest) monster for the starting dungeon.

<<set $monster_list={
slime:{
name: "Slime",
strength: 0,
dexterity: 0,
constitution: 0,
max_hp: 50,
current_hp:50,
gold:5,
item_list: ["rusty dagger", "rags", "stick"],
},

}>>

I will be adding 2 more for the first dungeon, but I have not yet determined how to make it randomly pick from those three monsters for just that dungeon, and a different set for a second dungeon.

Ideally I want to use the same code to do this, A-lah a function. Have the code pass the location to the function, so that the function then selects the monster list for that area, randomly picks one of those monsters, and passes the object of that monster (slime, in the above) back to begin combat against it.

Which, my unfamiliarity with this particular coding language is making hard.
You should consider not having a general monster list as a SugarCube variable because it will increase your save file and browsers like Chrome don't have a lot of local storage for saves. Anything you can make as constants or general templates should be done with javascript variables.

Then, if you must add specific monsters, like randomly selected for a fight, you add them as SugarCube variables to keep while they live/until the fight ends. Then you can either <<unset>> the variables, or reuse them later if you have general variables like $monster1, $monster2.

Or if the specific monsters only exist in a single passage, you can use temporary variables: _monster1, _monster2
 

DuskSarsis

Newbie
Apr 30, 2019
61
67
The individual monsters will be local to a dungeon or "region", but that dungeon will have multiple passages as they move around it. I noticed tags I can put on each passage and wonder if those can be used to indicate the region for the particular monster table to pull from? I haven't yet seen anything that references those in twine/sugarcube code though.

I.E. I currently have a cave with 3 rooms that I am using for testing until I get monsters and combat working how I want. Each room they enter needs to run the encounter chance function, then produce a fight from monsters specific to that dungeon, but where the list of potential monsters is the same for each room of that dungeon. I will then add a fourth room that is a different monster table and shouldn't pull any of those three.

For the intro dungeon, it's a slime, goblin, and tentacle trap. each monster should have stats specific to that monster type as well as specific abilities they can use (randomly choosing between an attack and their abilities, possibly can add variables to decide what they use at a later date but starting simple).

It IS true I don't want all of the monsters variables in a players save file though, I would rather that be loaded separately when they launch the game, not when loading a save since the variables for that should be static and never change (other than current hp during a fight, which I can probably redo as a local variable anyways _currenthp). But I am not yet sure how to do that. I'll look into that. Preferably the game_state and player_character variables are the only things they need to save...

but monsters present me with a problem. There will be a LOT of them and I don't want to bog down the writing by redoing or copying the code into hundreds of areas to construct the monster objects with every passage they might be encountered. That is part of why I wanted to set up a function. If I can do a function that has all of the monsters values saved to it, and can take the region, then find the matching array of pre-constructed monster objects that is not part of the global variables, that would work. but can that be done in Twine/Sugarcube?
 

Alcahest

Engaged Member
Donor
Game Developer
Jul 28, 2017
3,625
4,486
The individual monsters will be local to a dungeon or "region", but that dungeon will have multiple passages as they move around it. I noticed tags I can put on each passage and wonder if those can be used to indicate the region for the particular monster table to pull from? I haven't yet seen anything that references those in twine/sugarcube code though.

I.E. I currently have a cave with 3 rooms that I am using for testing until I get monsters and combat working how I want. Each room they enter needs to run the encounter chance function, then produce a fight from monsters specific to that dungeon, but where the list of potential monsters is the same for each room of that dungeon. I will then add a fourth room that is a different monster table and shouldn't pull any of those three.

For the intro dungeon, it's a slime, goblin, and tentacle trap. each monster should have stats specific to that monster type as well as specific abilities they can use (randomly choosing between an attack and their abilities, possibly can add variables to decide what they use at a later date but starting simple).

It IS true I don't want all of the monsters variables in a players save file though, I would rather that be loaded separately when they launch the game, not when loading a save since the variables for that should be static and never change (other than current hp during a fight, which I can probably redo as a local variable anyways _currenthp). But I am not yet sure how to do that. I'll look into that. Preferably the game_state and player_character variables are the only things they need to save...

but monsters present me with a problem. There will be a LOT of them and I don't want to bog down the writing by redoing or copying the code into hundreds of areas to construct the monster objects with every passage they might be encountered. That is part of why I wanted to set up a function. If I can do a function that has all of the monsters values saved to it, and can take the region, then find the matching array of pre-constructed monster objects that is not part of the global variables, that would work. but can that be done in Twine/Sugarcube?
The things you talk about are no problem. Anything can be done with javascript if there is no good way to do it with SugarCube, or if you just prefer doing it with javascript to begin with. For functions that must return a value, javascript is the way to go. As with javascript variables, the setup object can also be used to easily access javascript functions from within SugarCube.

For the "region", yes, you can use tags <<if tags().includes("region1")>>
Or, if there is only one exit from the region/fight, you could simply set a variable that decides which monster list to use while in the region.
 

DuskSarsis

Newbie
Apr 30, 2019
61
67
The things you talk about are no problem. Anything can be done with javascript if there is no good way to do it with SugarCube, or if you just prefer doing it with javascript to begin with. For functions that must return a value, javascript is the way to go. As with javascript variables, the setup object can also be used to easily access javascript functions from within SugarCube.

For the "region", yes, you can use tags <<if tags().includes("region1")>>
Or, if there is only one exit from the region/fight, you could simply set a variable that decides which monster list to use while in the region.
Excellent! As for Javascript... well, to be honest, I hate javascript with a passion. :D It was the single most annoying language to work with and I haven't in years so I don't really remember any of it. I'll have to for this, clearly, but I would prefer to do it as little as possible. Having the tags thing is a lot of help though! I can use that to make it choose which monster table to use.

Still, if I can get encounters and combat working, the rest I should be able to do no problem. Guess I should ask how to convert that slime earlier into javascript then. I can start constructing the first three monsters and set up the first encounter.
 

Alcahest

Engaged Member
Donor
Game Developer
Jul 28, 2017
3,625
4,486
Excellent! As for Javascript... well, to be honest, I hate javascript with a passion. :D It was the single most annoying language to work with and I haven't in years so I don't really remember any of it. I'll have to for this, clearly, but I would prefer to do it as little as possible. Having the tags thing is a lot of help though! I can use that to make it choose which monster table to use.

Still, if I can get encounters and combat working, the rest I should be able to do no problem. Guess I should ask how to convert that slime earlier into javascript then. I can start constructing the first three monsters and set up the first encounter.
You're not thinking of java? Just asking because people often confuse the two languages. Javascript is something else.

The monster_list variable is essentially javascript already (SugarCube is written in javascript), just attached to a SugarCube variable. Could be added to the javascript section for instance like this:
JavaScript:
setup.monster_list = {
    slime:{
        name: "Slime",
        strength: 0,
        dexterity: 0,
        constitution: 0,
        max_hp: 50,
        current_hp:50,
        gold:5,
        item_list: ["rusty dagger", "rags", "stick"],
    },
};
 

DuskSarsis

Newbie
Apr 30, 2019
61
67
I probably am thinking java. Though I remain woefully unfamiliar with javascript. Currently I am trying to populate an array for the first dungeon that should run when the player enters a dungeon tagged passage. However, it does not currently seem to work. It kinda does in the sense I can get it to return object Object, or undefined instead of an error, buuut that's not really what I want it to return since it needs to populate the array with all of the objects information so that I can then pass it to the combat script when triggered...

Code:
setup.monster_list = {
    slime:{
        name: "Slime",
        strength: 0,
        dexterity: 0,
        constitution: 0,
          intelligence: 0,
        max_hp: 50,
        current_hp:50,
        gold:5,
        item_list: ["bones", "copper_ring"],
        ability_list: [],
    },
      goblin:{
        name: "Goblin",
        strength: 1,
        dexterity: 2,
        constitution: 0,
          intelligence: 0,
        max_hp: 75,
        current_hp: 75,
        gold:15,
        item_list: ["rusty_dagger", "rags"],
        ability_list: ["grope"],
    },
      tentacle_trap:{
        name: "Tentacle Trap",
        strength: 3,
        dexterity: 0,
        constitution: 0,
         intelligence: 0,
          max_hp: 80,
          current_hp: 80,
          gold:20,
          item_list: ["stick"],
        ability_list: ["bind", "caress"],
    },
 
};

if (tags().includes("dungeon1")) {
    setup.monster_table = [];
      setup.monster_table.push(setup.monster_list.slime);
      setup.monster_table.push(setup.monster_list.goblin);
      setup.monster_table.push(setup.monster_list.tentacle_trap);
};
and for testing I have

<<print setup.monster_table>> in various forms at the top of the page
<<print setup.monster_table.slime.name>> <-- ideally the one I want to work, for ease of readability
<<print setup.monster_table.setup.monster_list.slime>>
<<print setup.monster_table.setup.monster_list.slime.name>>

As I change things around I can sometimes get the object Object return, or undefined return, but never the actual values of the objects I am attempting to put into the array. I keep trying to look how to do things as I work but invariably what I find for putting things in arrays, never uses a pre-defined object. but I KNOW that it can... at least pretty sure that it can.

On the upside, the gallery works flawlessly so I can easily set that up to unlock as the player progresses now...?
 

guest1492

Member
Apr 28, 2018
359
299
The <<print>> macro only works if you are trying to print a string, or the data type of the variable you are printing has a toString method.

Since you are familiar with programming, I want to discuss deep vs shallow copying. SugarCube variables are deep copied with every passage transition, therefore object references break. So consider something like this:
Code:
<<set $a = [1, 2, 3]>>
<<set $b = $a>>
<<print $b>>
/* displays 1, 2, 3 */

/* passage transition takes place */
<<set $a.push(4)>>
<<print $b>>
/* displays 1, 2, 3 */
I figure it might be relevant since you are doing a lot of work with objects. Note that the setup variable you are using is not a SugarCube variable so this doesn't apply.

Don't know exactly how you plan to make encounters work, but you might want to take a look at . You can check if the player is inside a dungeon passage and whether or not he/she is already in combat. If not in combat, then roll a die to see if they continue exploration as normal or if they get thrown into combat.

The above is so that you don't get a bunch of "junk" added to your play history. (Also note that the <<goto>> macro does not suspend execution of following code in a passage. Everything in a passage is executed before navigating away.)
 
Last edited:

DuskSarsis

Newbie
Apr 30, 2019
61
67
The <<print>> macro only works if you are trying to print a string, or the data type of the variable you are printing has a toString method.

Since you are familiar with programming, I want to discuss deep vs shallow copying. SugarCube variables are deep copied with every passage transition, therefore object references break. So consider something like this:
Code:
<<set $a = [1, 2, 3]>>
<<set $b = $a>>
<<print $b>>
/* displays 1, 2, 3 */

/* passage transition takes place */
<<set $a.push(4)>>
<<print $b>>
/* displays 1, 2, 3 */
I figure it might be relevant since you are doing a lot of work with objects. Note that the setup variable you are using is not a SugarCube variable so this doesn't apply.

Don't know exactly how you plan to make encounters work, but you might want to take a look at . You can check if the player is inside a dungeon passage and whether or not he/she is already in combat. If not in combat, then roll a die to see if they continue exploration as normal or if they get thrown into combat.

The above is so that you don't get a bunch of "junk" added to your play history. (Also note that the <<goto>> macro does not suspend execution of following code in a passage. Everything in a passage is executed before navigating away.)
A friend of mine familiar with javascript is gonna help me out a bit. I am somewhat familiar with coding but I am by no means at a professional level. Not familiar enough to know deep or shallow copying...

That said, the combat wont result in a "you died" passage or navigation change on defeat. Instead I intend for it to replace the combat screen with a monster rape (or submission) scene, which returns you to the passage you were on when you click to continue, albeit with 1hp.

I do have a return script already working for my StoryMenu passages, so I can probably just add a "scene" tag so that it doesn't change the return destination when you trigger a scene, and can return back same as the menu tags do. Or just use the menu tag on scene pages, but that's not as clean.

Code:
$(document).on(":passagestart", function (event) {
if (!tags().includes("menu")) {
State.variables.return = passage();
}
});