SingleEvent Scripts: Difference between revisions

From PMDOWiki
Palika (talk | contribs)
m added serpent import
Palika (talk | contribs)
added crit HP status stuff
Line 138: Line 138:
# Next, you need to modify how <code>CheckDefeatedGangLeader</code> identifies the win condition. If the win condition is failed, the function should return early. The default one does this by checking all enemy teams, and if any members on any team are not dead, then that means an enemy is still alive and the win condition has failed.  
# Next, you need to modify how <code>CheckDefeatedGangLeader</code> identifies the win condition. If the win condition is failed, the function should return early. The default one does this by checking all enemy teams, and if any members on any team are not dead, then that means an enemy is still alive and the win condition has failed.  
#* For our example, if our gang leader is a Scrafty, we should iterate through all enemy teams and see if we find a member that is both a Scrafty and alive. If so, then our win condition has failed and we should return early.
#* For our example, if our gang leader is a Scrafty, we should iterate through all enemy teams and see if we find a member that is both a Scrafty and alive. If so, then our win condition has failed and we should return early.
#* Alternatively, you can assign a LuaTable property to the Scrafty that marks it as, say, <code>'GangLeader' = true</code>, and make sure that there are no NPCs with a LuaTable property of 'GangLeader' equal to true that are alive. Doing it like this would allow you to have as many GangLeaders as you may want that need to be defeated to meet the win condition.
#* Alternatively, you can assign a LuaTable property to the Scrafty that marks it as, say, <code>.GangLeader = true</code>, and make sure that there are no NPCs with a LuaTable property of 'GangLeader' equal to true that are alive. Doing it like this would allow you to have as many GangLeaders as you may want that need to be defeated to meet the win condition.
# With all this done, when the Scrafty is defeated, the map should end and call <code>end_sequence()</code> as long as we aren't watching a replay. <code>end_sequence()</code> is responsible for fading the map and music out.
# With all this done, when the Scrafty is defeated, the map should end and call <code>end_sequence()</code> as long as we aren't watching a replay. <code>end_sequence()</code> is responsible for fading the map and music out.
#* You can redefine it to anything you want if you want different things to happen when you actually win. For example, maybe you want your party members to face the camera and do a pose for a few seconds before the game fades out. You could accomplish this by writing code at the very top of end_sequence that makes all your party members do the face down and do the Pose animation!
#* You can redefine it to anything you want if you want different things to happen when you actually win. For example, maybe you want your party members to face the camera and do a pose for a few seconds before the game fades out. You could accomplish this by writing code at the very top of end_sequence that makes all your party members do the face down and do the Pose animation!
#* You can do anything of course, but be careful not to remove things that may be important to handling the end of the dungeon adventure, such as the line of code responsible for ending the segment as Cleared.
#* You can do anything of course, but be careful not to remove things that may be important to handling the end of the dungeon adventure, such as the line of code responsible for ending the segment as Cleared.
== Causing a Wipe if allies faint: ==
Want to cause a party wipe in the event that a specific character faints? Perhaps the Hero/Partner characters? This is easily achievable again via pre-made Lua scripting to be placed within event_single.lua.
--Check to make sure nobody marked as "IsPartner" is dead. Check guests as well
--if one is dead, then cause an instant game over
--if someone died and they aren't "IsPartner", then send them home automatically.
function SINGLE_CHAR_SCRIPT.AllyDeathCheck(owner, ownerChar, context, args)
local player_count = GAME:GetPlayerPartyCount()
local guest_count = GAME:GetPlayerGuestCount()
if player_count < 1 then return end--If there's no party members then we dont need to do anything
for i = 0, player_count - 1, 1 do
local player = GAME:GetPlayerPartyMember(i)
if player.Dead and player.IsPartner then --someone died
for j = 0, player_count - 1, 1 do --beam everyone else out
player = GAME:GetPlayerPartyMember(j)
if not player.Dead then--dont beam out whoever died
--delay between beam outs
GAME:WaitFrames(60)
TASK:WaitTask(_DUNGEON:ProcessBattleFX(player, player, _DATA.SendHomeFX))
player.Dead = true
end
end
--beam out guests
for j = 0, guest_count - 1, 1 do --beam everyone else out
guest = GAME:GetPlayerGuestMember(j)
if not guest.Dead then--dont beam out whoever died
--delay between beam outs
GAME:WaitFrames(60)
TASK:WaitTask(_DUNGEON:ProcessBattleFX(guest, guest, _DATA.SendHomeFX))
guest.Dead = true
end
end
return--cut the script short here if someone died, no need to check guests
end
end
--check guests as well
if guest_count < 1 then return end--If there's no guest members then we dont need to do anything
for i = 0, guest_count - 1, 1 do
local guest = GAME:GetPlayerGuestMember(i)
if guest.Dead and guest.IsPartner then --someone died
--beam player's team out first
for j = 0, player_count - 1, 1 do --beam everyone else out
player = GAME:GetPlayerPartyMember(j)
if not player.Dead then--dont beam out whoever died
TASK:WaitTask(_DUNGEON:ProcessBattleFX(player, player, _DATA.SendHomeFX))
player.Dead = true
GAME:WaitFrames(60)
end
end
for j = 0, guest_count - 1, 1 do --beam everyone else out
guest = GAME:GetPlayerGuestMember(j)
if not guest.Dead then--dont beam out whoever died
TASK:WaitTask(_DUNGEON:ProcessBattleFX(guest, guest, _DATA.SendHomeFX))
guest.Dead = true
GAME:WaitFrames(60)
end
end
end
end
end
== Giving Exclamation points to low health allies ==
Want that cool little effect where characters low on HP have an exclamation point over their head? Using some pre-configured event_single Lua code and by configuring your own Critical Health status, you can achieve this cosmetic effect pretty easily.
First, let's configure the status effect that'll give the player the exclamation point over their head. Go to the Data tab on the Dev menu, then go to Statuses. Click the Add button on the menu that pops up, and it's here we'll configure our Critical Health status. Configure yours to match what you see in the image below. Anything you don't see in the window in this image can be left as the defaults.
[[File:CriticalHealthStatus.PNG]]
Next, insert this code into your event_single.lua file for your mod.
Code:
function SINGLE_CHAR_SCRIPT.SetCriticalHealthStatus(owner, ownerChar, context, args)
local player_count = GAME:GetPlayerPartyCount()
local critical = RogueEssence.Dungeon.StatusEffect("critical_health")
--initialize status data before adding it to anything
critical:LoadFromData()
for i = 0, player_count - 1, 1 do
local player = GAME:GetPlayerPartyMember(i)
if player.HP <= player.MaxHP / 4 and player:GetStatusEffect("critical_health") == nil then
TASK:WaitTask(player:AddStatusEffect(nil, critical, false))
elseif player.HP > player.MaxHP / 4 and player:GetStatusEffect("critical_health") ~= nil then
TASK:WaitTask(player:RemoveStatusEffect("critical_health"))
end
end
end
Once you've done that, we need to configure Universal to run this code at the end of turns. Click the Constants tab on the Dev menu, go to Universal, and scroll down until you find "On Turn Ends". Here, we'll add a SingleCharScriptEvent that calls SetCriticalHealthStatus on the end of turns, so the Critical Health status that displays the exclamation point is set or removed when someone's HP passes the 1/4 HP threshold. Your configuration should look like this in the end:
[[File:LowHealthExclamation.PNG]]
You should be all set! Your ally team members (not guests, you can expand this functionality yourself if you so wish to cover guests) will get an exclamation above their head and an informative status when they're low on HP.
[[File:LowHP.PNG]]

Revision as of 21:25, 13 March 2024

These events go into the event_single.lua file of your mod. As of version v0.7.25, you will need to copy this file from base PMDO into your mod first, then paste these scripts into it.


Custom Boss Fight Win Conditions / End effects:

Author: Palika

Want to have it so a boss fight has an alternative win condition than just "beat all enemies"? Want something special to happen when that win condition is met? If the answer is yes, you'll need to override the usual BeginBattleEvent and its corresponding CheckBossClearEvent and end_sequence responsible for these features with the Lua version.

Code

---
-- Reimplementation of Audino's C# event for BeginBattleEvent.
-- This will allow us to inject custom function code before ending a battle 
-- (such as clearing existing lava flows when a boss fight ends).
-- @tparam GameEventOwner owner
-- @tparam Character ownerChar
-- @tparam SingleCharContext context
-- @tparam table args Table of format {CustomClearEvent="NameOfFunctionThatRunsOnClear", [other arguments to said script]}
function SINGLE_CHAR_SCRIPT.LuaBeginBattleEvent(owner, ownerChar, context, args)
	local MapCheckState = luanet.import_type('RogueEssence.Dungeon.MapCheckState')
	local SingleCharScriptEvent = luanet.import_type('RogueEssence.Dungeon.SingleCharScriptEvent')
	
	local map_clear_idx = 'map_clear_check'

	if context.User ~= nil then return end
	--if a custom clear event is not given, use the default one.
	if args.CustomClearEvent == nil then args.CustomClearEvent = 'LuaCheckBossClearEvent' end
	
	--Turn on Team Mode if allowed when the boss fight starts.
	if _DUNGEON:CanUseTeamMode() then
		_DUNGEON:SetTeamMode(true)
	end
	
	local clear_status = RogueEssence.Dungeon.MapStatus(map_clear_idx)
	clear_status:LoadFromData()
	
	local check = clear_status.StatusStates:GetWithDefault(luanet.ctype(MapCheckState))
	--The 2nd argument in the function below needs a string that represents a lua table of the arguments to pass. Serpent.line will convert the lua table to a string representing it for us.
	--We only NEED to pass args, as owner, ownerchar, and context are automatically passed in when the check event is called
	check.CheckEvents:Add(SingleCharScriptEvent(args.CustomClearEvent, Serpent.line(args)))
	--check.CheckEvents:Add(LuaCheckBossClearEvent(owner, ownerChar, context, args))
	
	TASK:WaitTask(_DUNGEON:AddMapStatus(clear_status))
end

--Import Serpent library.
Serpent = require 'lib.serpent'

---
-- Reimplementation of the basic CheckBossClearEvent. 
-- Call something different from LuaBeginBattleEvent or edit this accordingly if you 
-- want a different wincon for your map or special effects/anims on win.
-- @tparam GameEventOwner owner
-- @tparam Character ownerChar Not used by this function.
-- @tparam SingleCharContext context Not used by this function.
-- @tparam table args Table of caller-defined arguments. Not used by this function.
function SINGLE_CHAR_SCRIPT.LuaCheckBossClearEvent(owner, ownerChar, context, args)
	local MapCheckState = luanet.import_type('RogueEssence.Dungeon.MapCheckState')
	local SingleCharScriptEvent = luanet.import_type('RogueEssence.Dungeon.SingleCharScriptEvent')
	
	---
	-- Sequence that runs when map is over. Fade out, cut the music, etc.
	-- @usage Coroutine
	function end_sequence()
		_GAME:BGM("", true)
		 
		TASK:WaitTask(_GAME:FadeOut(false))
		 
		_DUNGEON:ResetTurns()
		
		-- restore all and remove all map status
		local statuses_to_remove = {}
		for i = 0, _ZONE.CurrentMap.Status.Keys.Count - 1, 1 do
			statuses_to_remove[i] = _ZONE.CurrentMap.Status.Keys[i]
		end
		
		for i = 0, #statuses_to_remove - 1, 1 do
			TASK:WaitTask(_DUNGEON:RemoveMapStatus(statuses_to_remove[i], false))
		end 
		
		-- heal everyone in the party
		for i = 0, GAME:GetPlayerPartyCount() - 1, 1 do
			_DATA.Save.ActiveTeam.Players[i]:FullRestore()
		end
		
		TASK:WaitTask(_GAME:EndSegment(RogueEssence.Data.GameProgress.ResultType.Cleared))

	end

	-- For each enemy team, check each chara in that team. 
	-- If any are still alive, then fail this check and return early.
	for i = 0, _ZONE.CurrentMap.MapTeams.Count - 1, 1 do 
		local team = _ZONE.CurrentMap.MapTeams[i].Players
		for j = 0, team.Count - 1, 1 do
			-- Break and return early if even one enemy is not dead.
			if not team[j].Dead then return end
		end
	end
	
	-- Everyone's dead, clear the scene.
	local checks = owner.StatusStates:GetWithDefault(luanet.ctype(MapCheckState))
	
	-- The call originally for this was to remove(this), which isn't in lua. 
	-- So we need to find the LuaCheckBossClearEvent and remove that (remove ourself)
	for i = 0, checks.CheckEvents.Count - 1, 1 do
		if LUA_ENGINE:TypeOf(checks.CheckEvents[i]) == luanet.ctype(SingleCharScriptEvent) then
			if checks.CheckEvents[i].Script == args.CustomClearEvent then
				checks.CheckEvents:Remove(checks.CheckEvents[i])
			end
		end
	end
	
	if _DATA.CurrentReplay == nil then
		TASK:WaitTask(end_sequence())
	else 
		TASK:WaitTask(_GAME:EndSegment(RogueEssence.Data.GameProgress.ResultType.Cleared))
	end
	
end

Usage

The first function, LuaBeginBattleEvent, is responsible for letting PMDO know to look at LuaCheckBossClearEvent to determine when the map is won. Instead of the usual BeginBattleEvent in your boss fight's OnMapStart's Map Effects, you need to call a SingleCharScriptEvent for LuaBeginBattleEvent instead. It'll look like this:

LuaCheckBossClearEvent is responsible for identifying when the map is won, and then the end_sequence function within it is responsible for fading stuff out and actually winning the map.

You won't need to modify the scripting for LuaBeginBattleEvent at all; it should remain as it is.

Tutorial: Alternate win condition

If you would like an alternate win condition or end sequence besides just fading out, then you will need to edit LuaCheckBossClearEvent and its end_sequence respectively.

  1. First, copy and rename your custom version of LuaCheckBossClearEvent to something more appropriately fitting.
    • Let's say we want a win condition where you only have to defeat the leader of a gang to win the boss fight. We'll call this function instead CheckDefeatedGangLeader. Copy the LuaCheckBossClearEvent function above and rename it to CheckDefeatedGangLeader.
  2. In LuaBeginBattleEvent, be sure to pass an argument of CustomClearEvent = 'CheckDefeatedGangLeader' when you define it in the MapEffects, so LuaBeginBattleEvent will knows to use your custom function instead of the default one given.
  3. Next, you need to modify how CheckDefeatedGangLeader identifies the win condition. If the win condition is failed, the function should return early. The default one does this by checking all enemy teams, and if any members on any team are not dead, then that means an enemy is still alive and the win condition has failed.
    • For our example, if our gang leader is a Scrafty, we should iterate through all enemy teams and see if we find a member that is both a Scrafty and alive. If so, then our win condition has failed and we should return early.
    • Alternatively, you can assign a LuaTable property to the Scrafty that marks it as, say, .GangLeader = true, and make sure that there are no NPCs with a LuaTable property of 'GangLeader' equal to true that are alive. Doing it like this would allow you to have as many GangLeaders as you may want that need to be defeated to meet the win condition.
  4. With all this done, when the Scrafty is defeated, the map should end and call end_sequence() as long as we aren't watching a replay. end_sequence() is responsible for fading the map and music out.
    • You can redefine it to anything you want if you want different things to happen when you actually win. For example, maybe you want your party members to face the camera and do a pose for a few seconds before the game fades out. You could accomplish this by writing code at the very top of end_sequence that makes all your party members do the face down and do the Pose animation!
    • You can do anything of course, but be careful not to remove things that may be important to handling the end of the dungeon adventure, such as the line of code responsible for ending the segment as Cleared.




Causing a Wipe if allies faint:

Want to cause a party wipe in the event that a specific character faints? Perhaps the Hero/Partner characters? This is easily achievable again via pre-made Lua scripting to be placed within event_single.lua.

--Check to make sure nobody marked as "IsPartner" is dead. Check guests as well --if one is dead, then cause an instant game over --if someone died and they aren't "IsPartner", then send them home automatically. function SINGLE_CHAR_SCRIPT.AllyDeathCheck(owner, ownerChar, context, args) local player_count = GAME:GetPlayerPartyCount() local guest_count = GAME:GetPlayerGuestCount() if player_count < 1 then return end--If there's no party members then we dont need to do anything for i = 0, player_count - 1, 1 do local player = GAME:GetPlayerPartyMember(i) if player.Dead and player.IsPartner then --someone died for j = 0, player_count - 1, 1 do --beam everyone else out player = GAME:GetPlayerPartyMember(j) if not player.Dead then--dont beam out whoever died --delay between beam outs GAME:WaitFrames(60) TASK:WaitTask(_DUNGEON:ProcessBattleFX(player, player, _DATA.SendHomeFX)) player.Dead = true end end --beam out guests for j = 0, guest_count - 1, 1 do --beam everyone else out guest = GAME:GetPlayerGuestMember(j) if not guest.Dead then--dont beam out whoever died --delay between beam outs GAME:WaitFrames(60) TASK:WaitTask(_DUNGEON:ProcessBattleFX(guest, guest, _DATA.SendHomeFX)) guest.Dead = true end end return--cut the script short here if someone died, no need to check guests end end

--check guests as well if guest_count < 1 then return end--If there's no guest members then we dont need to do anything for i = 0, guest_count - 1, 1 do local guest = GAME:GetPlayerGuestMember(i) if guest.Dead and guest.IsPartner then --someone died --beam player's team out first for j = 0, player_count - 1, 1 do --beam everyone else out player = GAME:GetPlayerPartyMember(j) if not player.Dead then--dont beam out whoever died TASK:WaitTask(_DUNGEON:ProcessBattleFX(player, player, _DATA.SendHomeFX)) player.Dead = true GAME:WaitFrames(60) end end for j = 0, guest_count - 1, 1 do --beam everyone else out guest = GAME:GetPlayerGuestMember(j) if not guest.Dead then--dont beam out whoever died TASK:WaitTask(_DUNGEON:ProcessBattleFX(guest, guest, _DATA.SendHomeFX)) guest.Dead = true GAME:WaitFrames(60) end end end end

end



Giving Exclamation points to low health allies

Want that cool little effect where characters low on HP have an exclamation point over their head? Using some pre-configured event_single Lua code and by configuring your own Critical Health status, you can achieve this cosmetic effect pretty easily.

First, let's configure the status effect that'll give the player the exclamation point over their head. Go to the Data tab on the Dev menu, then go to Statuses. Click the Add button on the menu that pops up, and it's here we'll configure our Critical Health status. Configure yours to match what you see in the image below. Anything you don't see in the window in this image can be left as the defaults.

Next, insert this code into your event_single.lua file for your mod.

Code: function SINGLE_CHAR_SCRIPT.SetCriticalHealthStatus(owner, ownerChar, context, args) local player_count = GAME:GetPlayerPartyCount() local critical = RogueEssence.Dungeon.StatusEffect("critical_health") --initialize status data before adding it to anything critical:LoadFromData() for i = 0, player_count - 1, 1 do local player = GAME:GetPlayerPartyMember(i) if player.HP <= player.MaxHP / 4 and player:GetStatusEffect("critical_health") == nil then TASK:WaitTask(player:AddStatusEffect(nil, critical, false)) elseif player.HP > player.MaxHP / 4 and player:GetStatusEffect("critical_health") ~= nil then TASK:WaitTask(player:RemoveStatusEffect("critical_health")) end end end

Once you've done that, we need to configure Universal to run this code at the end of turns. Click the Constants tab on the Dev menu, go to Universal, and scroll down until you find "On Turn Ends". Here, we'll add a SingleCharScriptEvent that calls SetCriticalHealthStatus on the end of turns, so the Critical Health status that displays the exclamation point is set or removed when someone's HP passes the 1/4 HP threshold. Your configuration should look like this in the end:

You should be all set! Your ally team members (not guests, you can expand this functionality yourself if you so wish to cover guests) will get an exclamation above their head and an informative status when they're low on HP.