Crew Capture Mod

The place to discuss scripting and game modifications for X4: Foundations.

Moderators: Moderators for English X Forum, Scripting / Modding Moderators

User avatar
cakeonslaught
Posts: 28
Joined: Wed, 27. Jul 11, 02:35
x3ap

Crew Capture Mod

Post by cakeonslaught » Sat, 20. Jan 24, 00:26

Hey Gang,

I have started learning how to create md scripts and wanted to start by adding something that is holding back my pirate playthroughs. I thought that it would be awesome if i could just chase down my enemies after they bailed from their ships, and capture them from the escape pod!

This is necessary when i'm playing as certain factions that are hated all around. I can't talk to anyone, so the only way to get crew would be to bait certain pirates into boarding an abandoned ship which i would claim before the pods reach. You guys may know about that exploit, and it takes a long time to find certain faction ships and get them into that situation.

So long story short, i need some examples of certain logic which i thought more experienced members may be able to direct me to. I'm not expecting anyone to do the work for me, of course i am actively looking through unpacked md scripts like boarding.xml and trying to find the relevant logic. Scriptproperties has a ton of info, and i have visual studio completing code but the fact that the scriptproperties.html doesnt work anymore is making it hard to read through the xml and find exactly what i'm looking for.

How to set_value the players current target, (edit: I found player.target identified in the player properties)
how to query the distance to target, (edit: I found the section in scriptproperties explaining that distanceto is a property of a position in relation to another position, so it's: $object.position.distanceto{$targetobject.position}
how to check empty crew slots, (edit: $object.people.free does this)
how to transfer specific number of crew from npc ship to player ship, (edit: transfer_people function does a transfer and takes capacity into account)
how to change faction relations, (edit: i found the add_faction_relation function in the common xml)
how to create notifications (edit: i found the show_notification function in the common xml)

If anyone has examples off-hand, like "hey look in blahblah.xml" that would help me out.

Below is my little work so far, pulling conditions from better piracy mod and Euclid's advice to other people. Not sure if i'm using instantiate correctly, but i'd like it to be watching for conditions pretty much all the time.
Edit: I updated the code with what i think will work, just looking to figure out how to make the crew prisoners at first, for immersion.

Code: Select all

<?xml version="1.0" encoding="utf-8"?>
<mdscript name="Dandy_Capture_Crew" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="md.xsd">
<!-- Dans script to enable capturing boarding pods by targeting and approaching them

When the player target is an escapepod

	if the player controlled ship is within 10m
	
		If there is free space, 
			transfer crew from the escape pod into the player ship as prisoners (up to the free crew slots available)
			Reduce the player rep with owner faction of the captured crew.
			create a notification regarding the capture.. 
	
		If the player has a full crew complement, 
			issue a message that there is no free space on their ship -->
	<cues>
		<cue name="CueDandyCapturePod" instantiate="true" namespace="this">
		<!-- the cue will fire when the conditions are met-->
			<conditions>
				<!-- dim a variable "target" and set it to the player's current target -->
				<set_value name="$target" exact="player.target"/>
				<!-- the target needs to exist and be operational (not destroyed) -->
				<check_value value="$target and $target.isoperational"/>
				<!-- the target should be a ship -->
				<check_value value="$target.isclass.ship" />
				<!-- if the player target has shiptype == "escapepod"  -->
				<check_value value="$target.type == escapepod"/>
				<!-- if the distance to the player target is less than 10m -->
				<check_value value="($target.position.distanceto{player.ship.position} < 10m"/>
				<!-- the target should not be player owned -->
				<check_value value="not $target.isplayerowned" />
				<!-- EUCLID: does player ship exists, is it not docked, is he not just standing in the ship, is it his ship -->
				<check_value value="player.ship and player.ship.parent.isclass.zone and player.occupiedship and player.ship.isplayerowned"/>
			</conditions>
			
			<!-- perform the specified actions below, when the above conditions are met -->
			<actions>
			<!-- set up variables to be used in the actions namespace-->
			<set_value name="$target" exact="player.target"/>
			<set_value name="$capturedcrew"/>

				<!-- if the player has free crew space	 -->
				<do_if value="player.ship.people.free > 0">
					<!-- transfer the crew, from the pod to the player ship (as prisoners), until the player ship is full, or the pod is empty-->
					<transfer_people object="player.ship" otherobject="$target" result="$capturedcrew"/>
					<!-- destroy the pod -->
					
					<!-- lower relations with offended faction -->
					<add_faction_relation faction="$target.trueowner" otherfaction="player" value="-1"/>
					<!-- notify the player about the capture -->
					<show_notification text="'%s captured %s on %s', [player.name,player.ship,$capturedcrew]"/>

				<!-- if the player has no free space -->
				<do_if value="player.ship.people.free == 0">
					<!-- notify the player that they dont have any crew space left -->
					<show_notification text="'%s has no more crew space available', player.ship"/>
					<!-- delay so that the message doesnt just keep appearing -->
					<delay exact="5s"/>
				</do_if>
			<delay exact="1s"/>
			</actions>
		</cue>
	</cues>

</mdscript>

User avatar
cakeonslaught
Posts: 28
Joined: Wed, 27. Jul 11, 02:35
x3ap

Re: Crew Capture Mod

Post by cakeonslaught » Sat, 20. Jan 24, 03:56

Hi All,

I learned alot just reviewing the common (events, functions, etc.) and scriptproperties (properties and objecttype tree).

My main question is: whenever i wanna update my script, i replace it in the extensions, but do i need to do a new game start to get the changes transferred? In my first couple tests, i could run into the target pod but nothing happened.

Updated code a few times. The prisoner property of the crew will be changed by setting their role to "prisoner" but i've not worked that far yet.

Code: Select all

<?xml version="1.0" encoding="utf-8"?>
<mdscript name="Dandy_Capture_Crew" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="md.xsd">
<!-- Dans script to enable capturing boarding pods by targeting and approaching them

When the player target is an escapepod

	if the player controlled ship is within 10m
	
		If there is free space, 
			transfer crew from the escape pod into the player ship as prisoners (up to the free crew slots available)
			Reduce the player rep with owner faction of the captured crew.
			create a notification regarding the capture.. 
	
		If the player has a full crew complement, 
			issue a message that there is no free space on their ship -->
	<cues>
		<cue name="CueDandyCapturePod" instantiate="true" namespace="this">
		<!-- the cue will fire when the conditions are met-->
			<conditions>
				<!-- dim a variable "target" and set it to the player's current target -->
				<set_value name="$target" exact="player.target"/>
				<!-- the target needs to exist and be operational (not destroyed) -->
				<check_value value="$target and $target.isoperational"/>
				<!-- the target should be a ship -->
				<check_value value="$target.isclass.ship" />
				<!-- if the player target has shiptype == "escapepod"  -->
				<check_value value="$target.type == shiptype.escapepod"/>
				<!-- if the distance to the player target is less than 10m -->
				<check_value value="($target.position.distanceto{player.ship.position}) < 10m"/>
				<!-- the target should not be player owned -->
				<check_value value="not $target.isplayerowned" />
				<!-- EUCLID: does player ship exists, is it not docked, is he not just standing in the ship, is it his ship -->
				<check_value value="player.ship and player.ship.parent.isclass.zone and player.occupiedship and player.ship.isplayerowned"/>
			</conditions>
			
			<!-- perform the specified actions below, when the above conditions are met -->
			<actions>
			<!-- set up variables to be used in the actions namespace-->
			<set_value name="$target" exact="player.target"/>
			<set_value name="$capturedcrew"/>

				<!-- if the player has free crew space	 -->
				<do_if value="player.ship.people.free > 0">
					<!-- transfer the crew, from the pod to the player ship (as prisoners), until the player ship is full, or the pod is empty-->
					<transfer_people object="player.ship" otherobject="$target" result="$capturedcrew">
						<existing_people people="$target.people.list"/>
					<transfer_people/>
					<!-- destroy the pod -->
					
					<!-- lower relations with offended faction -->
					<add_faction_relation faction="$target.trueowner" otherfaction="player" value="-1"/>
					<!-- notify the player about the capture -->
					<show_notification text="'%s captured %s on %s', [player.name,player.ship,$capturedcrew]"/>

				<!-- if the player has no free space -->
				<do_if value="player.ship.people.free == 0">
					<!-- notify the player that they dont have any crew space left -->
					<show_notification text="'%s has no more crew space available', player.ship"/>
					<!-- delay so that the message doesnt just keep appearing -->
					<delay exact="5s"/>
				</do_if>
			<delay exact="1s"/>
			</actions>
		</cue>
	</cues>

</mdscript>

User avatar
euclid
Moderator (Script&Mod)
Moderator (Script&Mod)
Posts: 13308
Joined: Sun, 15. Feb 04, 20:12
x4

Re: Crew Capture Mod

Post by euclid » Sat, 20. Jan 24, 14:10

Without an event the cue won't fire at all. Not sure this is the best (as it will trigger often although it won't meet the condiditons) but try event_player_changed_target where event.param is the target. Also if there are several conditions which all have to be met, then use <check_all> .... </check_all>. Often several conditions can be covered in one check_value by using "and" as you did in the last condition. Finally 10m is very close, depending on your ship it well may be within the ship's hull. For testing purpose I suggest to increase that value.

Hope that helps ;-)

Cheers Euclid
"In any special doctrine of nature there can be only as much proper science as there is mathematics therein.”
- Immanuel Kant (1724-1804), Metaphysical Foundations of the Science of Nature, 4:470, 1786

User avatar
euclid
Moderator (Script&Mod)
Moderator (Script&Mod)
Posts: 13308
Joined: Sun, 15. Feb 04, 20:12
x4

Re: Crew Capture Mod

Post by euclid » Sat, 20. Jan 24, 14:39

I've spotted something else: <delay .... can only be used just before <actions>. Also 5s is very long and I'm not sure why you would need any delay here.

Concerning the use of "instantiate": Cues will fire once (if the conditions are met) and then close, meaning they won't listening anymore. To activate it again you would have to reset it. But if a cue is instantiated then, as the name suggests, it will create an instant of itself (i.e. a copy) and only the copy will fire (if the conditions are met) and then close while the original remains active, i.e. it will keep listening.

I also recomment you activate the debug log (if you do not have that done already) and check the log file for errors. Just add -logfile debuglog.txt to the executable (see sticky here).

Cheers Euclid
"In any special doctrine of nature there can be only as much proper science as there is mathematics therein.”
- Immanuel Kant (1724-1804), Metaphysical Foundations of the Science of Nature, 4:470, 1786

User avatar
cakeonslaught
Posts: 28
Joined: Wed, 27. Jul 11, 02:35
x3ap

Re: Crew Capture Mod

Post by cakeonslaught » Sat, 20. Jan 24, 22:49

Hi Euclid,

Thanks so much for your advice. I confirmed via debug messages that my script never cued.

Like you said, i had set it up wrong. I think i understand now. Cues will fire in two modes. Either they will be triggered by an event, in which case the event needs to be in the conditions, or they will be triggered on a timer. In the case of instantiation, having a cue set up on timer means that it keeps checking at that interval and takes up computation time when it might not even be relevant to check conditions.

So, im gonna try to figure out the event_scan_finished as the trigger here. Seems like event.param is the scanned object so i'm thinking that makes sense as the trigger. Get close, scan it, and the script should trigger.

One thing im confused about. event.param seems to be used in general, so how does the md script know the context of what event we are concerned with? For example, if event_scan_finished triggers and then 1ms later event_object_killed triggers?

Edit: debug is helping me a lot. Found some syntax errors. Especially, you cant use > < symbols for greater or less logic. It needs to be "lt" for less than and "gt" for greater than.
Last edited by cakeonslaught on Sat, 20. Jan 24, 23:45, edited 1 time in total.

User avatar
euclid
Moderator (Script&Mod)
Moderator (Script&Mod)
Posts: 13308
Joined: Sun, 15. Feb 04, 20:12
x4

Re: Crew Capture Mod

Post by euclid » Sat, 20. Jan 24, 23:41

Be careful with <event_scan ... because it will apply to all scans, also to those performed by NPCs (like police). You can use it but then make sure to add a condition to ensure the player does the scan. Better use <event_player_changed_activity activity="activity.scan" />

Concerning the event parameters they are independent (if that's what you are asking). Even if it is the same object, in your case the scan object and shortly after it is killed, the parameter will be the same. The only difference is that if you continue to use the parameter in the actions node, it will be null (as the object does not exist anymore). This is why you will find in many scripts additional conditions like <do_if value="event.param"> which ensures that the following is done only if event.param exists (i.e. not null in this case).

I thought about what your are trying to do and think that it could be a nice mod for the general purpose of acquiring crew (not only for a pirate player). Hence I'm looking forward to your mod and might use it myself if you're going to publish it eventually :-)

Cheers Euclid
"In any special doctrine of nature there can be only as much proper science as there is mathematics therein.”
- Immanuel Kant (1724-1804), Metaphysical Foundations of the Science of Nature, 4:470, 1786

User avatar
cakeonslaught
Posts: 28
Joined: Wed, 27. Jul 11, 02:35
x3ap

Re: Crew Capture Mod

Post by cakeonslaught » Sun, 21. Jan 24, 02:04

Hi Euclid and All,

Thanks for support.

I did manage to get this working. Tested several times and was able to reassign crew normally. They seem to work as expected.

The debug log was really helpful. I have it to the point where no debug errors are returned anymore.

There's a couple small things i wanna iron out:
- I used the "event_player_changed_target", because the scan event did not return the scanner or scanned (not sure why). It's not very robust for preventing accidental captures.
- I cant figure out how to set morale to 0. The "set_skill" and "add_skill" functions don't have parameters for setting the skill values, i couldn't understand how to use them nor find any examples.
- I noticed my log messages are not saved in the log, once the disappear, i cant review them.
- crew capacity seems to be counting the pilot, since i am getting 2 prisoners but have 3 capacity. I can fix this later i think.

Code: Select all

<?xml version="1.0" encoding="utf-8"?>
<mdscript name="Dandy_Capture_Crew" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="md.xsd">
<!-- Dans script to enable capturing crew from escape pods-->
	<cues>
		<cue name="CueDandyCapturePod" instantiate="true" namespace="this">
		<!-- the cue will fire when the conditions are met-->
			<conditions>
				<check_all>
					<!-- trigger the cue to start when a scan is completed -->
					<event_player_changed_target/>
					<!-- dim a variable "target" and set it to the event object being scanned -->
					<set_value name="$target" exact="player.target"/>
					<!-- the target needs to exist and be operational (not destroyed) -->
					<check_value value="$target and $target.isoperational"/>
					<!-- the target should be a ship -->
					<check_value value="$target.isclass.ship" />
					<!-- if the player target has shiptype == "escapepod"  -->
					<check_value value="$target.type == shiptype.escapepod"/>
					<!-- if the distance to the player target is less than 100m -->
					<check_value value="player.entity.controlled.bboxdistanceto.{$target} lt 100m"/>
					<!-- the target should not be player owned -->
					<check_value value="not $target.isplayerowned" />
					<!-- EUCLID: does player ship exists, is it not docked, is he not just standing in the ship, is it his ship -->
					<check_value value="player.ship and player.ship.parent.isclass.zone and player.occupiedship and player.ship.isplayerowned"/>
				</check_all>
			</conditions>
			
			<!-- perform the specified actions below, when the above conditions are met -->
			<actions>
				<debug_text text="'dandy debug: cue triggered'" chance="1"/>
				<!-- set up variables to be used in the actions namespace-->
				<set_value name="$target" exact="player.target"/>
				<set_value name="$capturedcrew"/>
				<debug_text text="'dandy debug: set values'" chance="1"/>
				<!-- if the player has free crew space	 -->
				<do_if value="player.ship.people.free gt 0">
					<debug_text text="'dandy debug: transfer started'" chance="1"/>
					<!-- transfer the crew, from the pod to the player ship (as prisoners), until the player ship is full, or the pod is empty-->
					<transfer_people object="player.ship" otherobject="$target" result="$capturedcrew">
						<existing_people people="$target.people.list"/>
					</transfer_people>
					<!-- kill all their morale, they just got blown up and imprisoned -->
					<do_all exact="$capturedcrew.count" counter="$i">
						<set_skill object="player.ship" template="$capturedcrew.{$i}" type="skilltype.morale" value="0"/>
						<set_npc_template_role object="player.ship" template="$capturedcrew.{$i}" role="entityrole.prisoner"/>
					</do_all>
					<debug_text text="'dandy debug: transfer ended'" chance="1"/>
					<!-- lower relations with offended faction -->
					<add_faction_relation faction="$target.trueowner" otherfaction="faction.player" value="-1" reason="relationchangereason.boardedobject"/>
					<debug_text text="'dandy debug: relation changed'" chance="1"/>
					<!-- notify the player about the capture -->
					<show_notification text="'%s captured %s prisoners aboard %s from %s'.[player.name,$capturedcrew.count,player.ship.name,$target.name]"/>
				</do_if>
				<!-- if the player has no free space -->
				<do_if value="player.ship.people.free == 0">
					<!-- notify the player that they dont have any crew space left -->
					<show_notification text="'%s has no more crew space available'.[player.ship.name]"/>
				</do_if>
				<debug_text text="'dandy debug: actions finished'" chance="1"/>
			</actions>
		</cue>
	</cues>
</mdscript>

User avatar
cakeonslaught
Posts: 28
Joined: Wed, 27. Jul 11, 02:35
x3ap

Re: Crew Capture Mod

Post by cakeonslaught » Sun, 21. Jan 24, 11:08

Hi All,

Some features are not finished but the core functionality works. So I uploaded it at modnexus in case anyone wants to try it.

It's just an md script running in its own namespace so i dont think there are potential incompatibilities here. Correct me if I'm wrong.

https://www.nexusmods.com/x4foundations/mods/1300

User avatar
cakeonslaught
Posts: 28
Joined: Wed, 27. Jul 11, 02:35
x3ap

Re: Crew Capture Mod

Post by cakeonslaught » Wed, 24. Jan 24, 12:54

Hi All,

Does anyone know about the difference between player.ship and player.controlled? In testing, my script seemed to stop working when i would swap to another ship to do piracy.

It seemed like player.controlled refers to the currently piloted object, but player.ship seemed to be locked onto the original ship i was piloting (the player's primary ship). I have no idea how the game identifies which ship is the player ship beyond some kind of GTA-style "time spent in the vehicle" system.

Anyway, i changed the reference method and it seemed to fix the issue.

EDIT: It did not fix the issue. It was saving and reloading that fixes the issue. For whatever reason, the script only fires when the player is in the ship that they loaded the game in. Changing ships confuses the script, so i need to look into why.

User avatar
cakeonslaught
Posts: 28
Joined: Wed, 27. Jul 11, 02:35
x3ap

Re: Crew Capture Mod

Post by cakeonslaught » Thu, 25. Jan 24, 11:29

Another update,

I fixed the issue. I had to create another cue in the script that will reset everything in case the player changes to scan mode. This way, the game starts looking at the ship you are flying when switching to scan mode, instead of just the ship that you were piloting when the game loaded.

The cue activated and started watching at game start, loading whatever ship into memory. Since it never reset, it was always checking just the one ship for conditionals. Now, you can switch ships without breaking the script.

j.harshaw
EGOSOFT
EGOSOFT
Posts: 1889
Joined: Mon, 23. Nov 15, 18:02

Re: Crew Capture Mod

Post by j.harshaw » Fri, 26. Jan 24, 00:10

cakeonslaught wrote:
Wed, 24. Jan 24, 12:54
Does anyone know about the difference between player.ship and player.controlled?
player.ship refers to the ship that the player happens to be on. In case it's a ship (A) that's docked on a ship (B), it will be A. This will be null if the player is not on a ship.
player.controlled refers to the ship that the player is actively controlling. This will similarly be null if the player is not controlling a ship.

HOWEVER if you use either in an event listener, it will be whichever ship fulfills that requirement when the script is parsed and will not change as the game runs. (Didn't see you use it in an event listener in your snippet above, but I saw Euclid suggest that you look into events, and the symptom sounds like such a case).

For such cases, you might want to take a look at the various global.$Player* groups defined in md.Setup (global.$PlayerContainerGroup, global.$PlayerControlledGroup, global.$PlayerOccupiedShipGroup, and global.$PlayerShipsGroup). These do update in runtime. Do check the slight differences in what these things refer to so you could use exactly what you need.

Welcome, have fun!

Return to “X4: Foundations - Scripts and Modding”