Skip to content
Jesuszilla edited this page Apr 17, 2024 · 2 revisions

Ikemen GO engine is written in golang programming language. On top of it the engine uses Lua as an embedded scripting language, allowing the application functionality to be adjusted after it has been built.

Among others, Lua scripts are responsible for following tasks:

  • Logic behind everything that is displayed outside fight (including screenpack logic, which controls interface rendering)
  • Features expanding match functionality: Stuff like continue screen, pause menu etc.)
  • Game modes logic: The engine is responsible for actual fighting. Who will enter the match and what to do with the match result is handled by external Lua scripts.
  • Game configuration

Default Lua scripts are available within external/script directory. Due to the fact that scripts are often updated and expanded between versions, any changes made to them locally may not be forward compatible with future engine releases (at least until the engine stabilizes feature wise). For this reason editing scrips directly is not recommended for normal players that use the engine as a Mugen replacement, but for full game makers who decide to branch off to tailor the scripts to work exactly as they need (or don't mind extra work in case of code conflicts).

Following sections briefly showcase some of the features that use Lua and at the same time don't require editing default Lua scripts directly. Article dedicated to Arcade Paths / Story Mode arcs also brings up the topic of external Lua scripts.

Explanation how Lua language works or how to edit default scripts to your liking is outside scope of this article. Refer to Lua 5.1 reference manual and dedicated sites specializing in teaching programming languages instead (e.g. 1, 2).

Current iteration of Lua scripts support in-game unlocking of characters, stages and game modes via optional select.def and screenpack parameters.

Character unlocking

Characters locked via select.def hidden parameter can be unlocked using unlock parameter assigned in the same line. Everything that follows unlock parameter is treated as pure Lua code (which means it has to be very last parameter in line). Lua code condition is executed each time the mode is initiated and upon returning to main menu. It should return boolean data type. If the code evaluates to boolean true the character locked via select.def hidden parameter becomes selectable. If the character has been unlocked, but later on the condition returns false, such character becomes locked again.

example 1

Below is an example of unlocking SuaveDude character based on data gathered during matches (by default used for generating hiscores).

When the game is started, data present within saves/stats.json file is read into a Lua table called stats (table structure is the same as in json file). Let's say you want to unlock the character only if player has finished arcade mode (made it to the end defeating final boss) at least once. In such case unlocking condition (pasted right after unlock = parameter in select.def line that adds hidden character) can look for example as follows:

stats.modes ~= nil and stats.modes.arcade ~= nil and stats.modes.arcade.clear >= 1

Above code will return true if condition is met, unlocking SuaveDude as a result, or false if player has not finished arcade mode at least once or deleted old stats.json file. Appropriate table existence checks are needed before reading the value - otherwise script execution would crash if the subtable is missing)

example 2

Let's say we want to make SuaveDude available in all modes, but not in "Boss Rush". In such case the unlock condition can look like this (using one of the listed functions that returns the same data as GameMode trigger)

gamemode() ~= "bossrush"

example 3

Due to how online play works (only inputs being sent over, which means both players essentially need the same game, including exactly the same looking and behaving select screens), having additional condition that ensures that characters and stages are unlocked during netplay is recommended. network() function returns true only in online modes, so it can be used for this purpose, for example like this:

(--[[here goes normal condition for unlocking, like in example 1 and 2]]) or network()

example 4

As explained in select.def, character parameters are defined in the same line as the character itself, which limits complexity of code that can be written there. For this reason, for more complicated conditions, it's worth to prepare Lua function (loaded via external modules functionality) and call it instead.

--function declared via external module
function myFunction(myArgument)
  --any Lua code goes here. The function should return boolean data type (true or false)
end

Stage unlocking

As above but the select.def unlock parameter is assigned as stage parameter.

Game Mode unlocking

Game Modes can be locked in screenpack DEF file, under [Title Info] section, by using special menu.unlock.<itemname> parameter (where <itemname> should replaced with itemname parameter associated with particular mode in main menu). Text assigned to this parameter is read as a pure Lua code and works in similar fashion to character and stages unlocking (mode will be visible only if the parameter is missing or if it returns true, otherwise the mode is hidden). Unlike characters and stages, game modes unlocking is runtime permanent (as soon as the mode is unlocked, the condition won't be checked again, until the game is restarted).

External lua scripts (let's call them modules) are meant to implement features coded in Lua without directly modifying scripts distributed with engine.

There are 3 alternative ways to load external modules. Modules are loaded after all default scripts in following order:

  1. Any file with lua extension placed in external/mods directory will be loaded automatically (alphabetical order)
  2. Paths to lua files can be added into save/config.json Modules table (in the same way as files are added to config.json CommonStates)
  3. Screenpack can be distributed with its own lua file, referenced in DEF file, under [Files] section, via module = path_to_lua_file parameter

Keep in mind that despite external modules making your script changes portable (easy to share and not being overwritten by the engine itself upon update), we can't promise that these files will be always compatible with future engine iterations. For example functions overwritten by a module may be changed in a way that your code is not ready for. Ikemen GO Lua code itself could also benefit from a major refactoring effort, so if anyone will ever bother to do it, it could lead to major changes to the scripts flow and logic.

In most cases using modules requires good understanding of scripts distributed with the engine (you need to know how something has been implemented in order to override how it works or expand its current functionality). As an example here is an external module code that adds completely new game mode, without need to edit any of the existing Lua scripts.

-- main.t_itemname is a table that stores game mode initialization functions. Each key of this table corresponds to itemname used in screenpack DEF file.
-- The table can be appended like any other Lua table. After doing so your new mode will work correctly with screenpack menu system.
-- Refer to external/script/main.lua to familiarize yourself with how this table looks like (selecting one of the existing modes as a base may be a good idea).
-- Check main.f_default() function that lists pre-made game mode flags that makes mode configuration easier (by default almost all flags that control pre-made functionalities within start.lua are set to false, so they have to be enabled here, if you want to use them).
-- Below code is just an example, not something that is meant to be used as it is.
main.t_itemname.mynewmode = function()
	main.f_playerInput(main.playerInput, 1) --keep this in all single player modes
	main.t_pIn[2] = 1 --keep it like this if you want p1 to have control over p2 side team menu selection
	main.charparam = {ai = true, music = true, single = true, stage = true, time = true} --which select.def charparam should be used
	main.lifebar = {match = true, p2ai = true} --which lifebar elements should be rendered
	main.matchWins = {draw = {0, 0}, simul = {1, 1}, single = {1, 1}, tag = {1, 1}} --amount of rounds to win for each team side and team mode
	main.storyboard = {credits = true, gameover = true} --which storyboards should be active
	main.teamMenu = {
		{ratio = true, simul = true, single = true, tag = true, turns = true}, --which team modes should be selectable by P1 side
		{ratio = true, simul = true, single = true, tag = true, turns = true}, --which team modes should be selectable by P2 side
	}
	main.versusScreen = true --if versus screen should be shown
	main.txt_mainSelect:update({text = 'Select screen title text'})
	setGameMode('awesomemode') --change it to string that you want GameMode CNS trigger to return
	main.luaPath = 'data/myscreenpack/awesomemode.lua' --path to script executed by start.f_selectMode() function. Refer to this article for information how to write script like this: https://github.com/K4thos/Ikemen_GO/wiki/wipMiscellaneous-Info/#arcs
	return start.f_selectMode --assigns which function should be called after mode initialization (same functions is used by all default modes but it can be overriden, if you prefer to write your own code)
end

This is enough to make your external mode functional within engine. Don't forget to also add it to your screenpack DEF file, as explained in this article.

Since external modules code is loaded after default lua scripts, while having access to everything initiated before it, it's also possible to overwrite any function or table present in default script, without touching the file directly. The more intrusive the change is the higher chance that it won't be forward compatible though.

Full list of functions externalized by engine into Lua is not documented at this point (you will have to dive into existing scripts to learn their names and how to use them for now). Instead the below list is limited only to Lua functions that works pretty much the same way as CNS/ZSS triggers (useful for implementing more advanced Arcade Paths / Story Mode arcs). Functions without additional comment either work like CNS/ZSS trigger equivalents or are self explanatory. Triggers that in CNS return so called boolean int here returns true or false instead of 1 or 0. All functions are case sensitive. They work from within Lua both in-match and after match.

Trigger Redirection

Redirection returns true if it successfully finds n-th player, or false otherwise. Lua "trigger" functions code used after redirection will be executed via matched player/helper, as long as new redirection is not used.

  • player(n)
  • parent(n)
  • root(n)
  • helper(n)
  • target(n)
  • partner(n)
  • enemy(n)
  • enemynear(n)
  • playerid(n)

Trigger with CNS equivalents

  • ailevel()
  • alive()
  • anim()
  • animelemlength()
  • animelemno(time_offset)
  • animelemtime(element)
  • animexist(animNo)
  • animlength()
  • animtime()
  • attack()
  • authorname()
  • backedge()
  • backedgebodydist()
  • bottomedge()
  • cameraposX()
  • cameraposY()
  • camerazoom()
  • canrecover()
  • combocount()
  • command("command_name")
  • consecutivewins()
  • const("param_name")
  • const240p(value)
  • const480p(value)
  • const720p(value)
  • ctrl()
  • defence()
  • dizzy()
  • dizzypoints()
  • dizzypointsmax()
  • drawgame()
  • facing()
  • firstattack()
  • framespercount()
  • frontedge()
  • frontedgebodydist()
  • frontedgedist()
  • fvar()
  • gamefps()
  • gameheight()
  • gamemode()
  • gametime()
  • gamewidth()
  • gethitvar("param_name")
  • getplayerid(player_no)
  • guardbreak()
  • guardpoints()
  • guardpointsmax()
  • hitcount()
  • hitdefattr(): returns string starting with S, C or A, followed by hitdef attributes separated by commas e.g. hitdefattr() == "S, HT"
  • hitfall()
  • hitover()
  • hitoverridden()
  • hitpausetime()
  • hitshakeover()
  • hitvelX()
  • hitvelY()
  • id()
  • incustomstate()
  • indialogue()
  • inguarddist()
  • isasserted("flag_name")
  • ishelper(optional_id)
  • ishometeam()
  • ishost()
  • leftedge()
  • life()
  • lifemax()
  • localscale()
  • lose()
  • loseko()
  • losetime()
  • majorversion()
  • map("map_name")
  • matchno()
  • matchover()
  • memberno()
  • movecontact()
  • movecountered()
  • moveguarded()
  • movehit()
  • movereversed()
  • movetype()
  • name(optional_playerno): optional argument value changes the trigger to work as equivalent of CNS P1Name, P2Name, P3Name, P4Name (relative to currently redirected player)
  • numenemy()
  • numexplod(optional_id)
  • numhelper(optional_id)
  • numpartner()
  • numproj()
  • numprojid(id)
  • numtarget(id)
  • p2bodydistX()
  • p2bodydistY()
  • p2distX()
  • p2distY()
  • p2life()
  • p2movetype()
  • p2stateno()
  • p2statetype()
  • palno()
  • parentdistX()
  • parentdistY()
  • pausetime()
  • physics()
  • playeridexist(id)
  • playerno()
  • posX()
  • posY()
  • power()
  • powermax()
  • prevstateno()
  • projcanceltime(id)
  • projcontacttime(id)
  • projguardedtime(id)
  • projhittime(id)
  • ratiolevel()
  • receiveddamage()
  • receivedhits()
  • redlife()
  • rightedge()
  • rootdistX()
  • rootdistY()
  • roundno()
  • roundsexisted()
  • roundstate()
  • roundtype()
  • score()
  • scoretotal()
  • screenheight()
  • screenposX()
  • screenposY()
  • screenwidth()
  • selfanimexist(anim_no)
  • selfstatenoexist(state_no)
  • stagebackedge()
  • stageconst()
  • stagefrontedge()
  • stagetime()
  • stagevar("param_name")
  • standby()
  • stateno()
  • statetype()
  • sysfvar(var_no)
  • sysvar(var_no)
  • teamleader()
  • teammode()
  • teamside()
  • teamsize()
  • tickspersecond()
  • time()
  • timeelapsed()
  • timemod(value)
  • timeremaining()
  • timetotal
  • topedge()
  • uniqhitcount()
  • var(var_no)
  • velX()
  • velY()
  • win()
  • winhyper()
  • winko()
  • winperfect()
  • winspecial()
  • wintime()

Lua/debug only triggers

  • animelemcount()
  • animelemtimesum()
  • animtimesum()
  • animowner()
  • attack()
  • continue()
  • defence()
  • displayname()
  • gameend()
  • gamespeed()
  • lasthitter(team_side)
  • localcoord()
  • matchtime()
  • network()
  • paused()
  • roundover()
  • roundstart()
  • selectno()
  • spritegroup()
  • spritenumber()
  • stateowner()
  • stateownerid()
  • stateownername()
  • tickcount()
  • vsync()
  • winnerteam()

Regardless of what kind of Lua code you're working on it's useful to have a way to dynamically print the result of it on screen. In order to do so start Ikemen Go with any command-line interpreter (Command Prompt on Windows, Terminal on unix, etc.). On Windows the easiest way to do it is creating a batch file.

Use Lua print() function to print out values or calculations on those values to command-line window. Basic information how to use it can be found in this tutorial.