New Core Ships - Design and Stats Discussion

It's not easy creating an entire faction or era. Post your work and collaborate in this forum.

Moderator: Forum Moderators

User avatar
ForestDragon
Posts: 1857
Joined: March 6th, 2014, 1:32 pm
Location: Ukraine

Re: New Core Ships - Design and Stats Discussion

Post by ForestDragon »

@Dalas Anyway, thinking further about transport, if it does get implemented, there are also some very important design questions like: What happens if a transport gets killed while having passengers?

Option A: passengers inside just die.

pros:
-slightly less likely to cause softlocks (but they are still very much possible depending on scenario design)

cons:
-for player ships, frustration of losing multiple valuable units just because one transport unit got killed (in a game where losing even one lvl2 or above unit is enough for many people to justify reloading a save)
-makes low-hp transports terrible to use
-makes the transport ability less flexible and not reusable for ground-based units where "sinking" isn't really applicable
-if transports can carry some flying/swimming units, there's not much reason for them to die from a ship sinking.

Option B: passengers are automatically dismebarked next to the ship when the ship dies

pros:
-much less frustration over losing a ship
-transport ability is more flexible for both naval and ground use

cons:
-more likely to cause softlocks if, for example, a ship carrying a leader is destroyed in the middle of the ocean and you can need your leader to reach a specified tile
-harder to implement "destroy enemy ships before they can disembark their crew" scenarios
My active add-ons: The Great Steppe Era,XP Bank,Alliances Mod,Pestilence,GSE+EoMa,Ogre Crusaders,Battle Royale,EoMaifier,Steppeifier,Hardcoreifier
My inactive add-ons (1.12): Tale of Alan, The Golden Age
Co-creator of Era of Magic
User avatar
IPS
Posts: 1384
Joined: December 6th, 2009, 6:36 pm
Location: Venezuela

Re: New Core Ships - Design and Stats Discussion

Post by IPS »

Btw, been viewing that you were talking about ships stats and viability.

I remember I faced this problem real long when meeting Rashy Era's Ship units. You can overview Rashy Era ships and see if anything suits to that, but as an additional note, I decided to give different resistances values to 40% and 50% water defenses ships instead of keeping both the same as Rashy Era used to be (by that I mean, I buffed res in 40% defenses one so they don't feel THAT BAD as they used to be...)

I hope you can take any ideas from rashy era ships values and see if that helps in the topic, but balancing ships to be fair and fun was not an easy task for me.
Creator of: Deathmatch new in 1.12 server.
Co-creator of: Era of Magic in 1.16 server
Developer of: Empires in 1.12 server, Ageless Era in 1.10 to 1.16 servers (but innactive recently)
Try My winning Orocia Guide
User avatar
Refumee
Posts: 286
Joined: February 12th, 2023, 10:17 am
Location: Vendraxis' Lair

Re: New Core Ships - Design and Stats Discussion

Post by Refumee »

Dalas120 wrote: January 1st, 2025, 5:36 pm
Ok, I'm going to make an attempt at writing a core-worthy transport ability based on PaBD. Will see if I can come up with something high-quality or not.
To get PaBD Code ready (for the player) we just need to adjust only some lines.
ForestDragon wrote: January 1st, 2025, 9:09 pm @Dalas Anyway, thinking further about transport, if it does get implemented, there are also some very important design questions like: What happens if a transport gets killed while having passengers?

Option A: passengers inside just die.

pros:
-slightly less likely to cause softlocks (but they are still very much possible depending on scenario design)

cons:
-for player ships, frustration of losing multiple valuable units just because one transport unit got killed (in a game where losing even one lvl2 or above unit is enough for many people to justify reloading a save)
-makes low-hp transports terrible to use
-makes the transport ability less flexible and not reusable for ground-based units where "sinking" isn't really applicable
-if transports can carry some flying/swimming units, there's not much reason for them to die from a ship sinking.

Option B: passengers are automatically dismebarked next to the ship when the ship dies

pros:
-much less frustration over losing a ship
-transport ability is more flexible for both naval and ground use

cons:
-more likely to cause softlocks if, for example, a ship carrying a leader is destroyed in the middle of the ocean and you can need your leader to reach a specified tile
-harder to implement "destroy enemy ships before they can disembark their crew" scenarios
In PaBD the passengers getting thrown out, up to 2 tiles, getting 8 fire damage and getting slowed.
User avatar
ForestDragon
Posts: 1857
Joined: March 6th, 2014, 1:32 pm
Location: Ukraine

Re: New Core Ships - Design and Stats Discussion

Post by ForestDragon »

Refumee wrote: January 2nd, 2025, 6:38 am In PaBD the passengers getting thrown out, up to 2 tiles, getting 8 fire damage and getting slowed.
Alright.
My active add-ons: The Great Steppe Era,XP Bank,Alliances Mod,Pestilence,GSE+EoMa,Ogre Crusaders,Battle Royale,EoMaifier,Steppeifier,Hardcoreifier
My inactive add-ons (1.12): Tale of Alan, The Golden Age
Co-creator of Era of Magic
User avatar
Refumee
Posts: 286
Joined: February 12th, 2023, 10:17 am
Location: Vendraxis' Lair

Re: New Core Ships - Design and Stats Discussion

Post by Refumee »

Dalas120 wrote: December 30th, 2024, 4:08 pm
[*]"Beachhead" ability: Units exiting this transport can immediately attack (but still cannot move).
[/list]
Can we discuss this ability?

I had different Ideas with it:
  • Can't move, can't attack
  • Can move, can't attack
  • Can't move, can attack (your ability)
  • Can move, can attack, but slowed
Idea Number 4 is AI compatible, as far as I know. Don't know if we need a specialized AI for the other ideas.
Dalas120
Posts: 202
Joined: July 5th, 2020, 6:51 pm

Re: New Core Ships - Design and Stats Discussion

Post by Dalas120 »

ForestDragon wrote: January 1st, 2025, 9:09 pm What happens if a transport gets killed while having passengers?
I'd been thinking that a dying ship would unload any units it can (onto unoccupied adjacent hexes with >0% defense), who'd become slowed and lose 50% of their hp. Any units who can't be unloaded would die.

My tentative plans are:
- transports cannot carry any `race=ship` units, but can carry fliers, merfolk, etc
- transports can load/unload units from/to any adjacent tile. Units cannot be unloaded onto terrain in which they'd have 0% defense.
- the player can't choose to load/unload one particular unit and not others; triggering a load will load every adjacent unit (up to the cargo limit), and triggering an unload will unload every carried unit (unless there's no valid hexes). I don't think it's possible to create a good enough UI to do things differently (or maybe a right-click menu could work?)
- let the player load/unload an unlimited number of times in a turn (also let them undo)
- when unloading units, prefer higher-defense terrain, and prefer the direction the ship is facing
- show a ship's cargo as overlays; scaled down unit images.
- simulate healing/poison/resting while inside ships. Thankfully none of those can kill units.
- create custom HAVEUNIT_TRANSPORT and SPEAKER_TRANSPORT macros, to be used cases where an event unit is in a transport
- [allow_undo], and make sure [on_undo] works right
- unload all units from transports at victory (ignoring placement restrictions)

All very tentative, of course, and I'm open to changing anything.
Refumee wrote: January 2nd, 2025, 10:19 am Idea Number 4 is AI compatible, as far as I know. Don't know if we need a specialized AI for the other ideas.
The default AI can't handle transports at all, can it? How would it know how to trigger a load/unload, or how to use a transport to move units from one place to another?

I'd imagined that AI transports would need to be given scripted scenario-specific behavior (e.g. we spawn ships with cargo at one end of the map, give them a [goto] to reach the shore, and once they get there we script an unload).
name
Developer
Posts: 607
Joined: January 6th, 2008, 3:32 am
Location: The United Kingdom of Great America and Northern Greenland

Re: New Core Ships - Design and Stats Discussion

Post by name »

Dalas120 wrote: January 2nd, 2025, 4:12 pm The default AI can't handle transports at all, can it? How would it know how to trigger a load/unload, or how to use a transport to move units from one place to another?
The default AI can handle unloading troops from ships in The High Seas since the ships use [tunnel] (which the default pathfinder knows how to use) to connect their "decks" (separate sections of the map that represent the deck of each ship) to all the tiles adjacent to the ship. The default AI will also board and fight to disable opposing ships in the same way. A player can do likewise and even capture an enemy ship by putting units on key parts of the deck (rudder and sail) to "man" it. Might be worth checking out.
User avatar
Roge_Tebnelok
Posts: 69
Joined: November 19th, 2022, 3:12 pm
Location: Янтарный Берег (Amber Coast/Bernsteinen Seeufer/Ravgul Strand-kant/Meripihka Rannan)/Elensefar

Re: New Core Ships - Design and Stats Discussion

Post by Roge_Tebnelok »

Aren't Derelict and Ghost ships possessed or something like kept together and driven by unnatural force? Shouldn't they in that case have lower arcane resistance than other ships? Like 30-40% with 60-80% as a standard for mechanical units?
Omniscience and omnipotence are one and the same.
User avatar
ForestDragon
Posts: 1857
Joined: March 6th, 2014, 1:32 pm
Location: Ukraine

Re: New Core Ships - Design and Stats Discussion

Post by ForestDragon »

Dalas120 wrote: January 2nd, 2025, 4:12 pm - simulate healing/poison/resting while inside ships. Thankfully none of those can kill units.
Would this be just manually simulating mainline healing/poison effects (so only regen +8 and regen +4 but not custom modded regeneration versions), or would it be a more general "trigger any [healing] or poison effects on a unit"?

Additionally, it might be a good idea to make healers in a transport heal all other units in the same transport, and having some kind of menu similar to the recall list to check current units inside a specific transport in more detail (it would also allow individual unloading, even if loading is still done for all adjacent units)
Dalas120 wrote: January 2nd, 2025, 4:12 pm - create custom HAVEUNIT_TRANSPORT and SPEAKER_TRANSPORT macros, to be used cases where an event unit is in a transport
Would it be a "match if the unit is on the map OR in transport" check or only check units in transport?
My active add-ons: The Great Steppe Era,XP Bank,Alliances Mod,Pestilence,GSE+EoMa,Ogre Crusaders,Battle Royale,EoMaifier,Steppeifier,Hardcoreifier
My inactive add-ons (1.12): Tale of Alan, The Golden Age
Co-creator of Era of Magic
User avatar
doofus-01
Art Director
Posts: 4178
Joined: January 6th, 2008, 9:27 pm
Location: USA

Re: New Core Ships - Design and Stats Discussion

Post by doofus-01 »

I won't weigh in too much on transport mechanics or traits/abilities, but maybe some situations can be helped or at least have more options if there are weaker support boats, maybe don't even have much of an attack, not that different from the old fake units. I got a few rough candidates, mostly based on the same hull, so the animations wouldn't be that difficult to apply to more than one of them.
skiff-examples.png
skiff-examples.png (16.74 KiB) Viewed 4959 times
Dalas120 wrote: January 2nd, 2025, 4:12 pm My tentative plans are:
- transports cannot carry any `race=ship` units, but can carry fliers, merfolk, etc
- transports can load/unload units from/to any adjacent tile. Units cannot be unloaded onto terrain in which they'd have 0% defense.
- the player can't choose to load/unload one particular unit and not others; triggering a load will load every adjacent unit (up to the cargo limit), and triggering an unload will unload every carried unit (unless there's no valid hexes). I don't think it's possible to create a good enough UI to do things differently (or maybe a right-click menu could work?)
- let the player load/unload an unlimited number of times in a turn (also let them undo)
- when unloading units, prefer higher-defense terrain, and prefer the direction the ship is facing
- show a ship's cargo as overlays; scaled down unit images.
- simulate healing/poison/resting while inside ships. Thankfully none of those can kill units.
- create custom HAVEUNIT_TRANSPORT and SPEAKER_TRANSPORT macros, to be used cases where an event unit is in a transport
- [allow_undo], and make sure [on_undo] works right
- unload all units from transports at victory (ignoring placement restrictions)

All very tentative, of course, and I'm open to changing anything.
The one thing I would say about the transport mechanics, and maybe this was already said and I just missed it, is that maybe it should all be kept separate from the base unit. It's a good idea to have an out-of-the-box solution for UMC, and not making it too complicated would be nice, but the issue with corner cases will always be there. Maybe the ongoing GUI work will have some effect on this anyway.
BfW 1.12 supported, but active development only for BfW 1.13/1.14: Bad Moon Rising | Trinity | Archaic Era |
| Abandoned: Tales of the Setting Sun
GitHub link for these projects
User avatar
Refumee
Posts: 286
Joined: February 12th, 2023, 10:17 am
Location: Vendraxis' Lair

Re: New Core Ships - Design and Stats Discussion

Post by Refumee »

doofus-01 wrote: January 5th, 2025, 1:25 am I won't weigh in too much on transport mechanics or traits/abilities, but maybe some situations can be helped or at least have more options if there are weaker support boats, maybe don't even have much of an attack, not that different from the old fake units. I got a few rough candidates, mostly based on the same hull, so the animations wouldn't be that difficult to apply to more than one of them.
skiff-examples.png
Dalas120 wrote: January 2nd, 2025, 4:12 pm My tentative plans are:
- transports cannot carry any `race=ship` units, but can carry fliers, merfolk, etc
- transports can load/unload units from/to any adjacent tile. Units cannot be unloaded onto terrain in which they'd have 0% defense.
- the player can't choose to load/unload one particular unit and not others; triggering a load will load every adjacent unit (up to the cargo limit), and triggering an unload will unload every carried unit (unless there's no valid hexes). I don't think it's possible to create a good enough UI to do things differently (or maybe a right-click menu could work?)
- let the player load/unload an unlimited number of times in a turn (also let them undo)
- when unloading units, prefer higher-defense terrain, and prefer the direction the ship is facing
- show a ship's cargo as overlays; scaled down unit images.
- simulate healing/poison/resting while inside ships. Thankfully none of those can kill units.
- create custom HAVEUNIT_TRANSPORT and SPEAKER_TRANSPORT macros, to be used cases where an event unit is in a transport
- [allow_undo], and make sure [on_undo] works right
- unload all units from transports at victory (ignoring placement restrictions)

All very tentative, of course, and I'm open to changing anything.
The one thing I would say about the transport mechanics, and maybe this was already said and I just missed it, is that maybe it should all be kept separate from the base unit. It's a good idea to have an out-of-the-box solution for UMC, and not making it too complicated would be nice, but the issue with corner cases will always be there. Maybe the ongoing GUI work will have some effect on this anyway.
I dont be greedy, but I am a saurian so it can't be helped. I want them all :O
Dalas120 wrote: January 2nd, 2025, 4:12 pm
ForestDragon wrote: January 1st, 2025, 9:09 pm What happens if a transport gets killed while having passengers?
I'd been thinking that a dying ship would unload any units it can (onto unoccupied adjacent hexes with >0% defense), who'd become slowed and lose 50% of their hp. Any units who can't be unloaded would die.

My tentative plans are:
- transports cannot carry any `race=ship` units, but can carry fliers, merfolk, etc
- transports can load/unload units from/to any adjacent tile. Units cannot be unloaded onto terrain in which they'd have 0% defense.
- the player can't choose to load/unload one particular unit and not others; triggering a load will load every adjacent unit (up to the cargo limit), and triggering an unload will unload every carried unit (unless there's no valid hexes). I don't think it's possible to create a good enough UI to do things differently (or maybe a right-click menu could work?)
- let the player load/unload an unlimited number of times in a turn (also let them undo)
- when unloading units, prefer higher-defense terrain, and prefer the direction the ship is facing
- show a ship's cargo as overlays; scaled down unit images.
- simulate healing/poison/resting while inside ships. Thankfully none of those can kill units.
- create custom HAVEUNIT_TRANSPORT and SPEAKER_TRANSPORT macros, to be used cases where an event unit is in a transport
- [allow_undo], and make sure [on_undo] works right
- unload all units from transports at victory (ignoring placement restrictions)

All very tentative, of course, and I'm open to changing anything.
Refumee wrote: January 2nd, 2025, 10:19 am Idea Number 4 is AI compatible, as far as I know. Don't know if we need a specialized AI for the other ideas.
The default AI can't handle transports at all, can it? How would it know how to trigger a load/unload, or how to use a transport to move units from one place to another?

I'd imagined that AI transports would need to be given scripted scenario-specific behavior (e.g. we spawn ships with cargo at one end of the map, give them a [goto] to reach the shore, and once they get there we script an unload).
Palms amid Blue Dunes Transportmechanic does almost all of it, execept:
- Units cannot be unloaded onto terrain in which they'd have 0% defense. (unsure, never tested it)
- when unloading units, prefer higher-defense terrain, and prefer the direction the ship is facing
- show a ship's cargo as overlays; scaled down unit images.
- simulate healing/poison/resting while inside ships. Thankfully none of those can kill units.
- create custom HAVEUNIT_TRANSPORT and SPEAKER_TRANSPORT macros, to be used cases where an event unit is in a transport (here lets the author unload the unit and then load)
- [allow_undo], and make sure [on_undo] works right (didn't test this as well)
User avatar
lhybrideur
Posts: 454
Joined: July 9th, 2019, 1:46 pm

Re: New Core Ships - Design and Stats Discussion

Post by lhybrideur »

For the Fireship, I would see a special status/ability called burning, where it loses some HP each turn (can kill) and is unhealable, or smth like that
User avatar
Temuchin Khan
Posts: 1844
Joined: September 3rd, 2004, 6:35 pm
Location: Player 6 on the original Agaia map

Re: New Core Ships - Design and Stats Discussion

Post by Temuchin Khan »

I like the new ships, overall.

I do wonder, however, if the Fireship should be a kamikaze unit.
Dalas120
Posts: 202
Joined: July 5th, 2020, 6:51 pm

Re: New Core Ships - Design and Stats Discussion

Post by Dalas120 »

Ok, here's a test implementation of ABILITY_TRANSPORT. Should be feature-complete, including undo.

`transport__board_unit` and `transport__debark_unit` can be called from anywhere, giving campaign authors a way to load/unload specific units in cutscenes or for scripted AI.

I decided not to allow healing inside transports. It'd be tricky to do (especially handling non-core heal/regen abilities, like ForestDragon mentioned), and both healers and transports are very powerful even without extra synergy. Poison and slow are still tracked.

I also decided not to `set canrecruit=yes` when a transport carries a leader. While this does avoid defeat from `defeat_condition=no_leader_left`, it also means that you can stick your leader in a transport to disable upkeep for both the transport and any carried units - I don't think that's a fun incentive for the player. Instead, campaign creators can set `defeat_condition=no_units_left` or `defeat_condition=never` plus a last breath/die event for their leader.

If anyone is interested in testing, please let me know how intuitive the implementation is and if you find any bugs.

Code: Select all



#########################
# TRANSPORT ABILITY DEFINITION
#########################
#define ABILITY_TRANSPORT
    # Canned definition of the transports ability to be included in an [abilities] clause.
    # this ability is EXPERIMENTAL, and details of its behavior may change. Please report any bugs to the Wesnoth github, forum, or discord
    [dummy]
        id=transport
        name=_"transport"
        description=_"This unit can carry a number of same-side passengers equal to its level. Cramped shipboard conditions prevent passengers from resting or healing each other, although poison will continue to take effect.

To board adjacent units or to debark passengers onto an adjacent hex, <b>mouse-over the unit/hex and press “t”, or right-click</b>."

        #########################
        # ASSIGN HOTKEYS
        #########################
        # Wesnoth doesn't allow multiple menu_items with the same hotkey
        # so create a single invisible menu_item here
        [event]
            name=unit placed
            id=transport__assign_hotkeys # we already have a transport__unit_placed
            [filter]
                ability=transport
            [/filter]
            [filter_condition]
                {VARIABLE_CONDITIONAL transport__hotkey_menu_item_set not_equals yes}
            [/filter_condition]
            {VARIABLE transport__hotkey_menu_item_set yes}
            [set_menu_item]
                id=transport_hotkey_menu_item
                use_hotkey=only
                [default_hotkey]
                    key=t
                [/default_hotkey]
                [filter_location]
                    [filter_adjacent_location]
                        [filter]
                            ability=transport
                        [/filter]
                    [/filter_adjacent_location]
                [/filter_location]
                
                [command]
                    #--------------------
                    # GET ALL CANDIDATE TRANSPORTS
                    #--------------------
                    [store_unit]
                        [filter]
                            side=$side_number
                            ability=transport
                            [filter_location]
                                [filter_adjacent_location]
                                    x,y=$x1,$y1
                                [/filter_adjacent_location]
                            [/filter_location]
                        [/filter]
                        variable=transportHotkey_transports
                    [/store_unit]
                    [if]
                        [have_unit]
                            x,y=$x1,$y1
                        [/have_unit]
                        #--------------------
                        # BOARD UNIT
                        #--------------------
                        # if we don't successfully board the first transport (maybe it's full), try every other option
                        [then]
                            [foreach]
                                array=transportHotkey_transports
                                variable=transportHotkey_transport
                                [do]
                                    [fire_event]
                                        name=transport__board_unit
                                        [primary_unit]
                                            id=$transportHotkey_transport.id
                                        [/primary_unit]
                                        [secondary_unit]
                                            x,y=$x1,$y1
                                        [/secondary_unit]
                                    [/fire_event]
                                    [if]
                                        [have_unit]
                                            x,y=$x1,$y1
                                        [/have_unit]
                                        [then]
                                        [/then]
                                        [else]
                                            [break]
                                            [/break]
                                        [/else]
                                    [/if]
                                [/do]
                            [/foreach]
                        [/then]
                        #--------------------
                        # DEBARK UNIT
                        #--------------------
                        # if we don't successfully debark the transport (maybe it's empty), try every other option
                        [else]
                            [foreach]
                                array=transportHotkey_transports
                                variable=transportHotkey_transport
                                [do]
                                    {VARIABLE transport__debark_hexes[0].x $x1}
                                    {VARIABLE transport__debark_hexes[0].y $y1}
                                    [fire_event]
                                        name=transport__debark_unit
                                        [primary_unit]
                                            id=$transportHotkey_transport.id
                                        [/primary_unit]
                                    [/fire_event]
                                    [if]
                                        [have_unit]
                                            x,y=$x1,$y1
                                        [/have_unit]
                                        [then]
                                            [break]
                                            [/break]
                                        [/then]
                                    [/if]
                                [/do]
                            [/foreach]
                        [/else]
                    [/if]
                    [allow_undo]
                    [/allow_undo]
                    {CLEAR_VARIABLE transportHotkey_transports,transportHotkey_transport,transportHotkey_passenger}
                [/command]
            [/set_menu_item]
        [/event]

        #########################
        # CREATE BOARDING MENU ITEM
        #########################
        # not meant to be called manually
        [event]
            name=unit placed
            id=transport__unit_placed
            first_time_only=no
            [filter]
                ability=transport
            [/filter]
            
            # use $unit.image_icon if available, otherwise use $unit.image
            [if] {VARIABLE_CONDITIONAL transport.image_icon.length greater_than 0}
                [then]
                    {VARIABLE thumbnail $unit.image_icon}
                [/then]
                [else]
                    {VARIABLE thumbnail $unit.image}
                [/else]
            [/if]
            
            #--------------------
            # CREATE BOARD MENU ITEM
            #--------------------
            # need to wrap this in another [event], or else there's no way to set delayed_variable_substitution=no for the menu's [filter]
            [event]
                name=set_board_menu_item
                id=transport__set_board_menu_item
                delayed_variable_substitution=no
                [set_menu_item]
                    id=board_$unit.id
                    description=_"Board $unit.type “$unit.name”"
                    image=$thumbnail|~SCALE(24,24)
                    [filter_location]
                        [filter]
                            side=$unit.side
                            [and]
                                side=$side_number
                            [/and]
                            [filter_adjacent]
                                id=$unit.id
                            [/filter_adjacent]
                            [not]
                                race=ship
                            [/not]
                            [not]
                                ability=transport
                            [/not]
                        [/filter]
                    [/filter_location]
                    [command]
                        [fire_event]
                            name=transport__board_unit
                            [primary_unit]
                                id=$unit.id
                            [/primary_unit]
                            [secondary_unit]
                                x,y=$|x1,$|y1
                            [/secondary_unit]
                        [/fire_event]
                        [allow_undo]
                        [/allow_undo]
                    [/command]
                [/set_menu_item]
            [/event]
            [fire_event]
                name=set_board_menu_item
            [/fire_event]
            
            {CLEAR_VARIABLE transport,thumbnail}
        [/event]
        
        #########################
        # BOARD UNIT ONTO TRANSPORT
        #########################
        # [primary_unit] is the transport
        # [secondary_unit] is the passenger
        # (optional) manually set transport__no_animation=yes to skip the [fake_unit_move] animation
        # (optional) manually set transport__silent_fail=yes to skip the [floating_text] from failed boarding
        # (optional) manually set transport__skip_checks=yes to skip checks when boarding (useful for cutscenes or undo)
        [event]
            name=transport__board_unit
            id=transport__board_unit
            first_time_only=no
            [filter]
                side=$side_number
            [/filter]
            [filter_second]
                side=$side_number
                [not]
                    race=ship
                [/not]
                [not]
                    ability=transport
                [/not]
            [/filter_second]
            
            #--------------------
            # CHECK MOVES
            #--------------------
            [if]{VARIABLE_CONDITIONAL second_unit.moves less_than 1}
                {VARIABLE_CONDITIONAL transport__skip_checks not_equals yes}
                [then]
                    [if] {VARIABLE_CONDITIONAL transport__silent_fail not_equals yes}
                        [then]
                            [floating_text]
                                x,y=$second_unit.x,$second_unit.y
                                text="<span color='#ff0000' size='small'>" + _"No Moves" + "</span>"
                            [/floating_text]
                        [/then]
                    [/if]
                [/then]
                #--------------------
                # CHECK CAPACITY
                #--------------------
                [elseif]
                    {VARIABLE_CONDITIONAL "passengers_$(  replace_all(replace_all('$unit.id',' ',''),'-','')  ).length" greater_than_equal_to $unit.level}
                    {VARIABLE_CONDITIONAL transport__skip_checks not_equals yes}
                    [then]
                        [if] {VARIABLE_CONDITIONAL transport__silent_fail not_equals yes}
                            [then]
                                # warn the player
                                [floating_text]
                                    x,y=$unit.x,$unit.y
                                    text="<span color='#ff0000' size='small'>" + _"Transport is Full" + "</span>"
                                [/floating_text]
                            [/then]
                        [/if]
                    [/then]
                [/elseif]
                [else]
                    #--------------------
                    # BOARD THE UNIT
                    #--------------------
                    [lua]
                        code= << wesnoth.game_events.fire('exit hex', wml.variables['second_unit'].x, wml.variables['second_unit'].y, wml.variables['unit'].x, wml.variables['unit'].y) >>
                    [/lua]
                    # can't fire 'enter hex' or 'moveto', because the transport occupies that hex and will become that event's $unit
                    [store_unit]
                        [filter]
                            id=$second_unit.id
                        [/filter]
                        variable="passengers_$(  replace_all(replace_all('$unit.id',' ',''),'-','')  )" # e.g. passengers_TransportGalleon4
                        mode=append
                        kill=yes
                    [/store_unit]
                    [if] {VARIABLE_CONDITIONAL transport__no_animation not_equals yes}
                        [then]
                            [move_unit_fake]
                                x=$second_unit.x,$unit.x
                                y=$second_unit.y,$unit.y
                                type=$second_unit.type
                                side=$second_unit.side
                            [/move_unit_fake]
                        [/then]
                    [/if]
                    [fire_event]
                        name=transport__refresh
                        [primary_unit]
                            id=$unit.id
                        [/primary_unit]
                    [/fire_event]
                    
                    #--------------------
                    # CREATE DEBARK MENU ITEM
                    #--------------------
                    # use $second_unit.image_icon if available, otherwise use $second_unit.image
                    [if] {VARIABLE_CONDITIONAL passenger.image_icon.length greater_than 0}
                        [then]
                            {VARIABLE thumbnail $second_unit.image_icon}
                        [/then]
                        [else]
                            {VARIABLE thumbnail $second_unit.image}
                        [/else]
                    [/if]
                    # need to wrap this in another [event], or else there's no way to set delayed_variable_substitution=no for the menu's [filter]
                    [event]
                        name=set_debark_menu_item
                        delayed_variable_substitution=no
                        id=transport__set_debark_menu_item
                        [set_menu_item]
                            id="debark_$(  replace_all(replace_all('$second_unit.id',' ',''),'-','')  )"
                            description=_"Debark $second_unit.type “$second_unit.name”"
                            image=$thumbnail~SCALE(24,24)
                            [filter_location]
                                [not]
                                    [filter]
                                    [/filter]
                                [/not]
                                [filter_adjacent_location]
                                    [filter]
                                        side=$side_number
                                        id=$unit.id
                                    [/filter]
                                [/filter_adjacent_location]
                            [/filter_location]
                            [command]
                                {VARIABLE transport__debark_hexes[0].x $|x1}
                                {VARIABLE transport__debark_hexes[0].y $|y1}
                                {VARIABLE transport__passenger_id $second_unit.id}
                                [fire_event]
                                    name=transport__debark_unit
                                    [primary_unit]
                                        id=$unit.id
                                    [/primary_unit]
                                [/fire_event]
                                [allow_undo]
                                [/allow_undo]
                            [/command]
                        [/set_menu_item]
                    [/event]
                    [fire_event]
                        name=set_debark_menu_item
                    [/fire_event]
                    
                    #--------------------
                    # UNDO BOARDING
                    #--------------------
                    # remember, delayed_variable_substitution=no inside the [on_undo],
                    # but delayed_variable_substitution=yes inside the [event]name=transport__debark_unit
                    [on_undo]
                        {VARIABLE transport__debark_hexes[0].x $second_unit.x}
                        {VARIABLE transport__debark_hexes[0].y $second_unit.y}
                        {VARIABLE transport__passenger_id $second_unit.id}
                        {VARIABLE transport__skip_checks yes}
                        [fire_event]
                            name=transport__debark_unit
                            [primary_unit]
                                id=$unit.id
                            [/primary_unit]
                        [/fire_event]
                        [modify_unit]
                            [filter]
                                id=$second_unit.id
                            [/filter]
                            moves=       $second_unit.moves
                            attacks_left=$second_unit.attacks_left
                            resting=     $second_unit.resting
                        [/modify_unit]
                    [/on_undo]
                [/else]
            [/if]
            {CLEAR_VARIABLE transport,passenger,thumbnail}
            {CLEAR_VARIABLE transport__no_animation,transport__silent_fail,transport__skip_checks}
        [/event]
        
        #########################
        # DEBARK ONE UNIT FROM TRANSPORT
        #########################
        # [primary_unit] is the transport
        # (optional) manually set transport__debark_hexes - expects at least 1 x,y pair
        # (optional) manually set transport__passenger_id to unload a specific passenger
        # (optional) manually set transport__no_animation=yes to skip the [fake_unit_move] animation
        # (optional) manually set transport__silent_fail=yes to skip the [floating_text] from failed boarding
        # (optional) manually set transport__skip_checks=yes to skip the terrain defense check (useful for undo or cutscenes)
        [event]
            name=transport__debark_unit
            id=transport__debark_unit
            first_time_only=no
            # don't filter by $side_number, or else this fails when a transport dies on someone else's turn
            
            [set_variables]
                name=transportDebark__passengers # copy into $passengers so that we can more easily modify the main array during this loop
                to_variable="passengers_$(  replace_all(replace_all('$unit.id',' ',''),'-','')  )"
            [/set_variables]
            [foreach]
                array=transportDebark__passengers
                variable=transportDebark__passenger
                [do]
                    #--------------------
                    # CHOOSE PASSENGER
                    #--------------------
                    # iterate through each passenger until we find the one (if any) that we're looking for
                    [if]{VARIABLE_CONDITIONAL transport__passenger_id not_equals $null}
                        {VARIABLE_CONDITIONAL transport__passenger_id not_equals $transportDebark__passenger.id}
                        [then]
                            [continue]
                            [/continue]
                        [/then]
                    [/if]
                    
                    #--------------------
                    # STORE LOCATIONS
                    #--------------------
                    # if transport__debark_hexes hasn't been provided, default to all adjacent hexes
                    [if]{VARIABLE_CONDITIONAL transport__debark_hexes.length equals 0}
                        [then]
                            [store_locations]
                                [filter_adjacent_location]
                                    x,y=$unit.x,$unit.y
                                [/filter_adjacent_location]
                                include_borders=no
                                variable=transport__debark_hexes
                            [/store_locations]
                        [/then]
                    [/if]
                    [foreach]
                        array=transport__debark_hexes
                        variable=transportDebark__hex
                        index_var=j
                        [do]
                            #--------------------
                            # CHECK PASSABILITY
                            #--------------------
                            [if]
                                [have_unit]
                                    x,y=$transportDebark__hex.x,$transportDebark__hex.y
                                [/have_unit]
                                # always check passability, even if transport__skip_checks=yes
                                [then]
                                    [continue]
                                    [/continue]
                                [/then]
                            [/if]
                            
                            #--------------------
                            # DEBARK THE UNIT
                            #--------------------
                            [unstore_unit]
                                variable=transportDebark__passenger
                                x,y=$transportDebark__hex.x,$transportDebark__hex.y
                            [/unstore_unit]
                            # can't fire 'exit hex', because the transport occupies that hex and will become that event's $unit
                            [lua]
                                code= << wesnoth.game_events.fire('enter hex', wml.variables['transportDebark__hex'].x, wml.variables['transportDebark__hex'].y, wml.variables['unit'].x, wml.variables['unit'].y) >>
                            [/lua]
                            [lua]
                                code= << wesnoth.game_events.fire('moveto',    wml.variables['transportDebark__hex'].x, wml.variables['transportDebark__hex'].y, wml.variables['unit'].x, wml.variables['unit'].y) >>
                            [/lua]
                            [store_relative_direction]
                                [source]
                                    x,y=$unit.x,$unit.y
                                [/source]
                                [destination]
                                    x,y=$transportDebark__hex.x,$transportDebark__hex.y
                                [/destination]
                                variable=transportDebark__facing
                            [/store_relative_direction]
                            [modify_unit]
                                [filter]
                                    id=$transportDebark__passenger.id
                                [/filter]
                                moves=0
                                attacks_left=0
                                resting=no
                                fainf=$transportDebark__facing
                            [/modify_unit] # if you change any of the debark effects, make sure to change boarding-undo as well
                            [hide_unit]
                                x,y=$transportDebark__hex.x,$transportDebark__hex.y
                            [/hide_unit]
                            
                            #--------------------
                            # CHECK TERRAIN
                            #--------------------
                            [if]
                                [have_unit]
                                    id=$transportDebark__passenger.id
                                    defense=100
                                [/have_unit]
                                {VARIABLE_CONDITIONAL transport__skip_checks not_equals yes}
                                [then]
                                    [kill]
                                        id=$transportDebark__passenger.id
                                    [/kill]
                                    [if] {VARIABLE_CONDITIONAL transport__silent_fail not_equals yes}
                                        [then]
                                            [floating_text]
                                                x,y=$transportDebark__hex.x,$transportDebark__hex.y
                                                text="<span color='#ff0000' size='small'>" + _"Impassable Terrain" + "</span>"
                                            [/floating_text]
                                        [/then]
                                    [/if]
                                    [continue]
                                    [/continue]
                                [/then]
                            [/if]
                            
                            #--------------------
                            # UPDATE UI
                            #--------------------
                            [clear_menu_item]
                                id="debark_$(  replace_all(replace_all('$transportDebark__passenger.id',' ',''),'-','')  )"
                            [/clear_menu_item]
                            {CLEAR_VARIABLE "passengers_$(  replace_all(replace_all('$unit.id',' ',''),'-','')  )[$i]"}
                            [fire_event]
                                name=transport__refresh
                                [primary_unit]
                                    id=$unit.id
                                [/primary_unit]
                            [/fire_event]
                            
                            #--------------------
                            # ANIMATE DEBARKING
                            #--------------------
                            [if] {VARIABLE_CONDITIONAL transport__no_animation not_equals yes}
                                [then]
                                    [move_unit_fake]
                                        x=$unit.x,$transportDebark__hex.x
                                        y=$unit.y,$transportDebark__hex.y
                                        type=$transportDebark__passenger.type
                                        side=$transportDebark__passenger.side
                                    [/move_unit_fake]
                                [/then]
                            [/if]
                            [unhide_unit]
                                x,y=$transportDebark__hex.x,$transportDebark__hex.y
                            [/unhide_unit]
                            
                            #--------------------
                            # UNDO DEBARKING
                            #--------------------
                            [on_undo]
                                {VARIABLE transport__skip_checks yes}
                                [fire_event]
                                    name=transport__board_unit
                                    [primary_unit]
                                        id=$unit.id
                                    [/primary_unit]
                                    [secondary_unit]
                                        id=$transportDebark__passenger.id
                                    [/secondary_unit]
                                [/fire_event]
                            [/on_undo]
                            [break]
                            [/break]
                        [/do]
                    [/foreach]
                [/do]
            [/foreach]
            {CLEAR_VARIABLE transportDebark__passengers,transportDebark__facing}
            {CLEAR_VARIABLE transport__debark_hexes,transport__passenger_id}
            {CLEAR_VARIABLE transport__no_animation,transport__silent_fail,transport__skip_checks}
        [/event]
        
        #########################
        # REFRESH TRANSPORT
        #########################
        # [primary_unit] is the transport
        [event]
            name=transport__refresh
            id=transport__refresh
            first_time_only=no
            
            #--------------------
            # RESET TRANSPORT
            #--------------------
            [remove_object]
                id=$unit.id
                object_id=passengers_overlay
            [/remove_object]
#             {MODIFY_UNIT id=$unit.id canrecruit no} # DISABLED (reasoning below)
            [if]
                [have_unit]
                    id=$unit.id
                    trait=loyal
                [/have_unit]
                [then]
                    {VARIABLE transportRefresh__upkeep 0}
                [/then]
                [else]
                    {VARIABLE transportRefresh__upkeep $unit.level}
                [/else]
            [/if]

            #--------------------
            # CHECK EACH PASSENGER
            #--------------------
            [foreach]
                array="passengers_$(  replace_all(replace_all('$unit.id',' ',''),'-','')  )"
                variable=transportRefresh__passenger
                [do]
                    {CLEAR_VARIABLE transportRefresh__poisonIPF}
                    [if] {VARIABLE_CONDITIONAL transportRefresh__passenger.status.poisoned equals yes}
                        [then]
                            {VARIABLE transportRefresh__poisonIPF "~G(70)"}
                        [/then]
                    [/if]
                    [object]
                        id=passengers_overlay
                        take_only_once=no
                        [filter]
                            id=$unit.id
                        [/filter]
                        [effect]
                            apply_to=overlay
                            add=misc/blank-hex.png~SCALE(144,144)~BLIT( $transportRefresh__passenger.image|~FL()~SCALE(42,42)$transportRefresh__poisonIPF, 36,"$($i*18-8)" )
                        [/effect]
                    [/object]
                    {CLEAR_VARIABLE transportRefresh__poisonIPF}
                    
                    # many scenarios end in defeat if the player has no leader
                    # so, if we load a leader, make the ship a leader
                    # DISABLED. 1) canrecruit=yes messes with passenger upkeep, and 2) modders can just use defeat_condition=never
#                     [if] {VARIABLE_CONDITIONAL transportRefresh__passenger.canrecruit equals yes}
#                         [then]
#                             {MODIFY_UNIT id=$unit.id canrecruit yes}
#                         [/then]
#                     [/if]
                    
                    # NOTE - if the transport has canrecruit=yes, the engine ignores its upkeep
                    # to avoid passengers getting free upkeep, I recommend against making transports leaders
                    [if] {VARIABLE_CONDITIONAL transportRefresh__passenger.upkeep equals "full"}
                        [then]
                            {VARIABLE_OP transportRefresh__upkeep add $transportRefresh__passenger.level}
                        [/then]
                    [/if]
                [/do]
            [/foreach]
            {MODIFY_UNIT id=$unit.id upkeep $transportRefresh__upkeep}
            {CLEAR_VARIABLE transportRefresh__upkeep}
            [allow_undo]
            [/allow_undo]
        [/event]
        
        #########################
        # TICK ALL TRANSPORTS
        #########################
        # should work fine if called manually
        [event]
            name=side turn  # apparently no such thing as "side turn refresh"
            id=transport__tick
            first_time_only=no
            [store_unit]
                [filter]
                    side=$side_number
                    ability=transport
                [/filter]
                variable=transportTick__transports
            [/store_unit]
            [foreach]
                array=transportTick__transports
                variable=transportTick__transport
                [do]
                    #--------------------
                    # HEALING, POISON, AND SLOW
                    #--------------------
                    [foreach]
                        array="passengers_$(  replace_all(replace_all('$transportTick__transport.id',' ',''),'-','')  )"
                        variable=transportTick__passenger
                        [do]
                            [lua]
                                code = <<
                                -- if the transport can heal or cure, record that
                                local transport_cures = false
                                local transport_slows = false
                                local transport_healing = 0
                                for ability in wml.child_range(wml.get_child(wml.variables['transportTick__transport'], 'abilities'), 'heals') do
                                    if ability.poison=='cured'  then transport_cures=true end
                                    if ability.poison=='slowed' then transport_slows=true end
                                    transport_healing = math.max( transport_healing, ability.value )
                                end
                                
                                -- if the passenger can self-cure, record that
                                local passenger_cures = false
                                local passenger_slows = false
                                for ability in wml.child_range(wml.get_child(wml.variables['transportTick__passenger'], 'abilities'), 'regenerate') do
                                    if ability.poison=='cured'  then passenger_cures=true end
                                    if ability.poison=='slowed' then passenger_slows=true end
                                end
                                
                                -- implement curing and healing
                                -- remember that units in transports can't rest, regenerate, or heal others
                                if wml.get_child(wml.variables['transportTick__passenger'],'status').poisoned then
                                    if     transport_cures or passenger_cures then
                                        wml.variables['transportTick__unpoison'] = false
                                    elseif transport_slows or passenger_slows then
                                        -- do nothing
                                    else
                                        wml.variables['transportTick__new_hitpoints'] = math.max(
                                            1,
                                            wml.variables['transportTick__passenger'].hitpoints-8
                                        )
                                    end
                                else
                                    wml.variables['transportTick__new_hitpoints'] = math.min(
                                        wml.variables['transportTick__passenger'].hitpoints+transport_healing,
                                        wml.variables['transportTick__passenger'].max_hitpoints
                                    )
                                end
                                >>
                            [/lua]
                            
                            # modifying these via lua doesn't seem to work; handle via WML instead
                            [if] {VARIABLE_CONDITIONAL transportTick__new_hitpoints not_equals $null}
                                [then]
                                    {VARIABLE transportTick__passenger.hitpoints $transportTick__new_hitpoints}
                                [/then]
                            [/if]
                            [if] {VARIABLE_CONDITIONAL transportTick__unpoison not_equals $null}
                                [then]
                                    {VARIABLE transportTick__passenger.status.poisoned no}
                                [/then]
                            [/if]
                            {VARIABLE transportTick__passenger.status.slowed no}
                            {CLEAR_VARIABLE transportTick__new_hitpoints,transportTick__unpoison}
                        [/do]
                    [/foreach]
                    
                    #--------------------
                    # REFERSH TRANSPORT
                    #--------------------
                    [fire_event]
                        name=transport__refresh
                        [primary_unit]
                            id=$transportTick__transport.id
                        [/primary_unit]
                    [/fire_event]
                [/do]
            [/foreach]
            {CLEAR_VARIABLE transportTick__transports}
        [/event]
        
        #########################
        # TRANSPORT DIES
        #########################
        # not meant to be called manually
        # when a transport dies, attempt to debark all passengers
        # any passengers who aren't able to debark die
        [event]
            name=last breath
            id=transport__last_breath
            first_time_only=no
            [filter]
                ability=transport
            [/filter]
            
            #--------------------
            # ATTEMPT TO DEBARK PASSENGERS
            #--------------------
            [set_variables]
                name=transportDie__passengers
                to_variable="passengers_$(  replace_all(replace_all('$unit.id',' ',''),'-','')  )"
            [/set_variables]
            [foreach]
                array=transportDie__passengers
                variable=transportDie__passenger
                [do]
                    # attempt to debark this passenger
                    {VARIABLE transport__passenger_id $transportDie__passenger.id}
                    {VARIABLE transport__silent_fail yes}
                    [fire_event]
                        name=transport__debark_unit
                        [primary_unit]
                            id=$unit.id
                        [/primary_unit]
                    [/fire_event]
                    
                    # if the passenger debarked, damage them for half of their current hp
                    {STORE_UNIT_VAR id=$transportDie__passenger.id hitpoints transportDie__damage}
                    {VARIABLE_OP transportDie__damage divide 2}
                    [harm_unit]
                        [filter]
                            id=$transportDie__passenger.id
                        [/filter]
                        amount=$transportDie__damage
                        kill=no
                        animate=yes
                        delay=0
                    [/harm_unit]
                    {CLEAR_VARIABLE transportDie__damage}
                [/do]
            [/foreach]
            {CLEAR_VARIABLE transportDie__passengers}
            
            #--------------------
            # KILL ANY REMAINING PASSENGERS
            #--------------------
            # let the ship properly die, so that we award XP to whoever killed it and fire any death events
            [event]
                name=die
                id=transport__die
                [filter]    
                    id=$unit.id
                [/filter]
                [kill]
                    id=$unit.id # remove the dead ship from the map, so we can kill remaining passengers in its hex
                [/kill]
                
                [set_variables]
                    name=transportDie__passengers
                    to_variable="passengers_$(  replace_all(replace_all('$unit.id',' ',''),'-','')  )"
                [/set_variables]
                [foreach]
                    array=transportDie__passengers
                    variable=transportDie__passenger
                    [do]
                        [unstore_unit]
                            variable=transportDie__passenger
                            x,y=$unit.x,$unit.y
                        [/unstore_unit]
                        [kill]
                            id=$transportDie__passenger.id
                            animate=yes
                            fire_event=yes
                        [/kill]
                        {CLEAR_VARIABLE "passengers_$(  replace_all(replace_all('$unit.id',' ',''),'-','')  )[$i]"}
                        [clear_menu_item]
                            id="debark_$(  replace_all(replace_all('$transportDie__passenger.id',' ',''),'-','')  )"
                        [/clear_menu_item]
                    [/do]
                [/foreach]
                {CLEAR_VARIABLE transportDie__passengers}
            [/event]
        [/event]
        
        #########################
        # DISEMBARK ON VICTORY
        #########################
        # don't let units stay on transports after the scenario ends, or we'd get all kinds of weird problems
        # this event can also be fired manually anytime we want to disembark all embarked units (e.g. before a cutscene)
        [event]
            name=victory,transport__victory
            id=transport__victory
            first_time_only=no
            [store_unit]
                [filter]
                    ability=transport
                [/filter]
                variable=transportVictory__transports
            [/store_unit]
            [foreach]
                array=transportVictory__transports
                variable=transportVictory__transport
                [do]
                    #--------------------
                    # DEBARK PASSENGERS
                    #--------------------
                    [set_variables]
                        name=transportVictory__passengers
                        to_variable="passengers_$(  replace_all(replace_all('$transportVictory__transport.id',' ',''),'-','')  )"
                    [/set_variables]
                    [foreach]
                        array=transportVictory__passengers
                        variable=transportVictory__passenger
                        [do]
                            # attempt to debark the unit
                            {VARIABLE transport__passenger_id $transportVictory__passenger.id}
                            {VARIABLE transport__silent_fail yes}
                            {VARIABLE transport__no_animation yes}
                            [fire_event]
                                name=transport__debark_unit
                                [primary_unit]
                                    id=$transportVictory__transport.id
                                [/primary_unit]
                            [/fire_event]
                            
                            # it's possible there's no adjacent free hexes in which to debark
                            # in that case, pick any empty hex and debark there
                            # this will also fail if every single hex on the map is filled, but that's very unlikely
                            [if]
                                [not]
                                    [have_unit]
                                        id=$transportVictory__passenger.id
                                    [/have_unit]
                                [/not]
                                [then]
                                    [store_locations]
                                        [not]
                                            [filter]
                                            [/filter]
                                        [/not]
                                        include_borders=no
                                        variable=transport__debark_hexes
                                    [/store_locations]
                                    {VARIABLE transport__passenger_id $transportVictory__passenger.id}
                                    {VARIABLE transport__no_animation yes}
                                    {VARIABLE transport__silent_fail yes}
                                    {VARIABLE transport__skip_checks yes}
                                    [fire_event]
                                        name=transport__debark_unit
                                        [primary_unit]
                                            id=$transportVictory__transport.id
                                        [/primary_unit]
                                    [/fire_event]
                                [/then]
                            [/if]
                        [/do]
                    [/foreach]
                    
                    #--------------------
                    # REFERSH TRANSPORT
                    #--------------------
                    [fire_event]
                        name=transport__refresh
                        [primary_unit]
                            id=transportVictory__transports
                        [/primary_unit]
                    [/fire_event]
                [/do]
            [/foreach]
            {CLEAR_VARIABLE transportVictory__transports,transportVictory__passengers}
        [/event]
    [/dummy]
#enddef

Last edited by Dalas120 on January 8th, 2025, 10:56 pm, edited 8 times in total.
User avatar
Ravana
Forum Moderator
Posts: 3313
Joined: January 29th, 2012, 12:49 am
Location: Estonia
Contact:

Re: New Core Ships - Design and Stats Discussion

Post by Ravana »

You forgot event id.
Post Reply