For “doing things the hard way”, when you want your equipment/skills/whatnot to do more than what vanilla does.
This page assumes you're using the standard Haxe symbols rather than thdoe Dicey csv-compatible ones (so “
and not ~
, ,
and not |
or [;]
, ||
and not #
). If you're writing directly to the csv (which is pretty common!), make sure to use the correct symbols.
The general format to do this is:
var [variable name] = new elements.[object type, Sentence case]([template to make object with]);
So if you want to make a Sword, (making care to replace quotation marks with tildes if manually editing the csv) you would do:
var coolsword = new elements.Equipment("Sword");
Or if you want a dummy fighter (for example Loud Bird):
var dummyenemy = new elements.Fighter("Loud Bird");
And if you want to make and reference a skill from template (for example Meganudge):
var dummyskill = new elements.Skill("Meganudge");
There are four ways to give equipment to the player:
var hookshot = new elements.Equipment("Hookshot"); hookshot.x = e.x; hookshot.y = e.y; self.equipment[self.equipment.indexOf(e)] = hookshot;
Since this will be instantaneous, you may want to add hookshot.animate(“flashandshake”) if this happens on-screen. (Note that e.animate(“flashandshake”) will not work because e is no longer in your equipment array.)
Dummy fighters can be used to check whether a status is set to alternate or not.
2022 UPDATE… HELLO FROM THE FUTURE WHERE RUNSCRIPT CAN BE USED TO ACCESS RULES ANYWHERE. var Rules = runscript(“getrules”);
where getrules.hx is just return Rules;
, then if(Rules.hasalternate(“ice”)) trace(“ice is alternate!”);
where you replace ice with whatever you want to check. the part about using dummy fighters to call combat-only scripts is still relevant except getequipment() doesn't exist anymore and i'm pretty sure getequipmentlist() can be used in generators
Say you want to check whether freeze is alternate:
var freezeisalt = false; var dummyfighter = new elements.Fighter("Wisp"); /*enemy is arbitrary as long as it's not immune to freeze*/ dummyfighter.addstatus("ice",1); for(stat in dummyfighter.status) { if(stat.type == "alternate_ice") { freezeisalt = true; } } if(freezeisalt) { /*do what you would do if freeze is alternate*/ } else { /*do what you would do if freeze is normal*/ }
They can also be used in non-combat environments to call code that normally only works in combat. For example, you could do this in a generator to get a list of all non-special equipment, even though getequipment() only works when called by a fighter:
var testdummy = new elements.Fighter("Wisp"); vat testskill = new elements.Skill("Meganudge"); testskill.script = "self.setvar(\"allequipment\",getequipment());"; testskill.execute(testdummy,testdummy); var allequipment = testdummy.getvar("allequipment");
Say you have a really long code snippet you need to copy and paste everywhere to get a custom status to work. You can reduce it a bit by making an entry in items.csv with that code in it, then declaring a skill with the entry name and executing it:
var dostatusstuff = new elements.Skill("internal status thing"); dostatusstuff.execute(self,target);
(self,target) means the fighter executing this script is you, and the fighter being targeted by the script is the enemy. This is pretty standard for most scripts.
Also, note that the AI will not be able to correctly handle executing skills. You should wrap the part with the skill declaration/execution in if(!simulation) { }. If the skill handles something that happens to the AI itself on its own turn, you're out of luck, and you'll need to unpack the script.
Note that using e will require a bit of kludgery since skill scripts have no way of telling what e is supposed to be unless a variable is explicitly assigned to e within the script. Have something like this at the start of the script you'll be executing, in this case for a skill named “internal item”:
var e = self.getvar("thisequipment");
And do this when you execute it:
var examplescript = new elements.Skill("internal item"); self.setvar("thisequipment",e); examplescript.execute(self,target); self.resetvar("thisequipment");
self.resetvar(“thisequipment”) is required since the game currently crashes if you end a battle while an equipment or array of equipment as one of your selfvars.
Scripts of skills can be manually modified. Here's a script for a card that executes a script of another random card you have, but needs some setup in order for things to work right:
var emulatethiseq = rand(self.equipment); var testscript = new elements.Skill("Against all odds_old"); testscript.script = emulatethiseq.script; self.setvar("thisequipment",e); testscript.script = "var e = self.getvar(\"thisequipment\"); " + testscript.script; self.resetvar("thisequipment"); testscript.execute(self,target);
Note that if there is sfx associated with the provided skill in equipmentsounds.json, it will play. Against all odds_old is a skill with no sfx that will likely not be removed or be given sfx any time soon.
Reversing the self and target parameters can let you do things like force the target to flee. The following snippet will cause the target to immediately run away, since Jetpack by default just contains “flee();”:
var runaway = new elements.Skill("Jetpack"); runaway.execute(target,self);
Since the game merely throws an error if a skill fails to execute rather than crashing or abandoning the original script, you can use skills as a sort of rudimentary try/catch mechanism. For example:
var trythis = new elements.Skill("code i'm not sure will work"); self.setvar("diditwork","no"); trythis.script = trythis.script + " self.setvar(\"diditwork\",\"yes\");"; trythis.execute(self,target); if(self.getvar("diditwork") == "yes") { /*do what you'd do if it works*/ } else { /*do what you'd do if it didn't work*/ }
Basically, fighters have their own script hooks for before combat, before start turn, on start turn, end turn, and after combat (last one might not be a thing?). So if the enemy is Sorceress, target.scriptbeforecombat would be the script that sets up her equipment layout. These script hooks can have scripts put in them as strings.
Equipment script hooks (before execute, on execute, on any equipment use, before start turn, etc.) are also accessible and can be modified.
Overall these are all the names you need to know:
Todo: go into more detail, look up what “on any countdown reduce” and “on fury” hook names are if they aren't exactly what they'd sound like.
Here's a function:
function specialenemychat(floor) { for (node in floor.nodes) { if (node.enemytemplate != null && node.enemytemplate.name == "Rhino Beetle") { node.enemytemplate.firstwords = "This is my special|boss-like dialogue|with 2 line breaks!||This is another dialogue box!"; node.enemytemplate.alwayssayfirstwords = true; } } };
Add this function to a generator. When you generate each floor, assign it to a variable (e.g. floorx where x is the floor number) the way the last floor is assigned to lastfloor. Then after the floor is generated, call specialenemychat with that floor variable as the argument. This will make Rhino Beetle have special first words, like a boss would, before every fight. (While you can also accomplish this by adding to the “first words” column in Rhino Beetle's .csv, you can not have the first words be picked randomly from a pool, whereas with generator tricks you could do node.enemytemplate.firstwords = rand([“Dialogue 1”,”Dialogue 2“]);)
Here's all the different things an enemy template possesses:
Note that changes to enemy templates will persist for the rest of the play session, including after you exit the current episode and play other episodes!
Todo: this is outdated! Do trace(target) and see what that spits out, maybe?
Example:
Shops, chests and trading booths also have templates, probably.
Call .toString() on a floor variable and you get some useful info. Todo: test this, actually write stuff.
Hoo boy where to start with this. Actuators as we know them are exploits regarding the Actuate library and the creation of a SimpleActuator - an object used to handle smooth tweening (sliding) animations for graphics - which allow for realtime delays before scripts are processed, and scripts that repeatedly activate over an interval of time. If done right, they should look like this:
if(self.getvar("myactuator") + 1 == "SimpleActuator1") { self.getvar("myactuator").stop(); self.resetvar("myactuator"); } var tw = new motion.actuators.SimpleActuator(null,0.1,null); var s = new elements.Skill("Against all odds_old"); s.script = " /*code stuff. quotes here should be backslashed e.g. \"*/ /*out clauses to stop the actuator should look like this:*/ if(things) { self.getvar(\"myactuator\").stop(); self.resetvar(\"myactuator\"); return; } /*the actuator should also stop if combat ends in any way. fighter.graphic is the literal graphic displayed by a fighter in battle; it's null if you're not in battle.*/ if(self.hp <= 0 || self.graphic == null || target == null || target.graphic == null || target.hp <= 0) { self.getvar(\"myactuator\").stop(); self.resetvar(\"myactuator\"); return; } /*if you're doing something like moving the player's equipment aside and giving them a few cards and a dice to make a selection; check whether self.doendturnnow ever turns true, and if so clean up immediately.*/ "; tw._repeat = -1; tw.onRepeat(s.execute,[self,target]); self.setvar("myactuator",tw); tw.move();
The second parameter in new motion.actuators.SimpleActuator is the initial delay and how much time in seconds passes between each repeat of the actuator. tw._repeat just determines how many times the actuator should repeat before it stops automatically. -1 means repeat forever, much like how an equipment with -1 uses is infinitely reuseable. Note tw and s in this case are both arbitrary variable names.
If for some reason you're dead-set on elegance or conciseness and want an actuator that only executes a single command (which almost definitely means it shouldn't repeat infinitely), the arguments taken by tw.onRepeat are a function and then an array of that function's arguments, so you could do for example:
tw.onRepeat(attack,[d]);
If you want an actuator that doesn't repeat, and simply acts like a delayed command rather than a while loop, keep in mind tw.onComplete. E.g.
var tw = new motion.actuators.SimpleActuator(null,0.01,null); var s = new elements.Skill("Against all odds_old"); s.script = "my awesome code"; tw.onComplete(s.execute,[self,target]); tw.move();
You can also construct an instance of the hscript parser and have onRepeat parse a string instead of executing it, which allows for more flexibility in what you can do with the actuator, but if you actually care about that then your name is either Jackeea, TheMysticSword, Wisp, or Kirb. Or Cody. Todo!
Larrytech is the idea of storing variables & functions normally only accessible in the generator in an array that can be accessed later ingame. The leading candidate is Rules.substitutions, thuogh that may be problematic if the game tries to serialize Rules.substitutions when you save and quit. Uses for this include changing the contents of the dungeon while ingame, e.g. in response to taking a certain level-up reward, picking a certain remix rule, or using an especially wacky item.
Usefulness of this tech (including whether it can be consistently and safely used at all) will be reported here as soon as my eyes stop glazing over and I get around to testing it.
TODO:
This is a gimmick that Tennis does, so you might not want to use it in your own mod as it will be associated with the extremely successful and famous mod Tennis that everyone totally played all the way through. Haha. Hahahaha. Ha. Alright, I'm done crying.
Normally, trying to fight Lady Luck will crash the game if you aren't in Backstage. This is because Lady Luck looks at your Backstage party to determine what Judgement cards she should spawn. Problem: If we're not in Backstage, we don't have a Backstage party.
Here's how Tennis gets around that. (Or, technically, a simplified version of Tennis for the purposes of demonstration.) In the “Change Floor” hook for the episode where you fight Lady Luck, this code is run (minus superfluous comments):
if(getcurrentfloor() == 6) { var kludge = new elements.Fighter('Monstermode Dummy'); if(kludge.hp == 1) { kludge.template.health = 2; kludge.template.skills[0] = 'Monstermode'; new elements.Fighter('Monstermode Dummy'); } Rules.monstermode = false; }
Note that a dummy fighter with 1 HP named “Monstermode Dummy” is already present in fighters.csv.
This code checks if the current floor is 6 (where Lady Luck is fought). If it is, it instantiates a new fighter named “Monstermode Dummy”. It then checks if Monstermode Dummy's HP is 1. If so, it sets Monstermode Dummy's *fighter template* to 2 (so that for the rest of the game session, whenever it is created, it will have 2 HP, so this branch will be skipped). Then it sets the first item of its template's “skills” field to “Monstermode”, and finally, re-instantiates the fighter. After all that, Rules.monstermode is manually set to false.
What was that all about? Well, look at lines 267-278 of Fighter.hx, which handle fighters (including the player) being initialized:
var firstskill:String = ""; if (template.skills.length > 0){ firstskill = template.skills[0]; } if (firstskill == "Monstermode"){ //A special case: Monster mode isn't exactly a new layout, but it does need to //override a lot of default behaviour. Monstermode.newrun(); equipment = []; for (i in 0 ... template.equipment.length){ Monstermode.add(template.equipment[i], false); }
This code is solely intended to check if we're starting Backstage. In episodes.csv, the skillcard for Backstage is listed as “Monstermode”, which is not a real card, and instead activates the special behavior seen above - namely, properly initializing Monstermode, including creating a party.
But there's a loophole here: at no point does the game check that the fighter with the “Monstermode” skill is the player. This may seem irrelevant since fighters.csv doesn't have a Skillcard column, but if we fetch a fighter's template, we can directly edit its “skills” array to include “Monstermode” as the first item.
This lets us start Monstermode by simply editing a fighter's template and then initializing that fighter. Which is what Tennis does.
Once we've initialized Monstermode, and our Backstage party exists (despite it probably being empty), we want to manually turn off Rules.monstermode
afterwards, since… well, 1., we don't actually want to use Backstage rules, and 2., if we didn't turn it off, the game would crash anyway upon entering any fight due to our party being empty. (Or just because our current fighter isn't in the party. Not sure.)
Now we can fight Lady Luck. Yay.
Further tweaks to the Lady Luck fight are left as an exercise to the reader, including: