Using OnAIUpdate with a device that takes time to recharge

A place to discuss mods in development and concepts for new mods.
User avatar
JohnBWatson
Fleet Officer
Fleet Officer
Posts: 1355
Joined: Tue Aug 19, 2014 10:17 pm

Mon Jun 27, 2016 11:58 pm

For an upcoming mod, I am attempting to give a ship an item that it will use every X time(under a certain set of constraints that I have already implemented, as seen below) using the OnAIUpdate function. Assuming it is not possible for the AI to directly check whether the device is charged or call the invoke function for it(and please do correct me if that isn't true, as that would clearly be the best way to do this if possible), what would be the best way to implement a timer that restricts the AI's use of the device?

In addition, does anyone know what powerToActivate is, exactly? It would be immensely helpful if I had a formula that could convert that value to an integer to be used by our makeshift timer.

Code: Select all

<ItemType UNID="&itIocrymJumpdrive;"
			name=				"Iocrym jumpdrive"
			attributes=			"alien, iocrym, majorItem"
			  
			level=				"8"
			frequency=			"rare"
			unknownType=		"&itUnknownAlienDevice;"

			value=				"25000"
			mass=				"7500"
			  
			description=		"This faster-than-light jumpdrive is purpose built for combat, allowing the Iocrym's fearsome Sentinel ships to rapidly close the distance between themselves and their targets."
			>

		<Image imageID="&rsItems1;" imageX="0" imageY="192" imageWidth="96" imageHeight="96"/>

		<MiscellaneousDevice
				powerUse=			"500"
				capacitorPowerUse=	"3000"
				powerToActivate=	"1000000"
				/>

		<Invoke key="J" installedOnly="true">
			; Jump to a position 20 ls away from 150 ls in the direction of the target
			; Use objGetTarget to determine the target
			; Use objIsShip to determine whether the target is a ship (for the AI)
			; Use objGetDistance to determine whether the target is in range (both for the AI and for here.)
			; Use sysVectorAngle to get angle from self to target
			; Use sysVectorRandom to get a random vector from newpos
			<!--
				If target is a ship
					newPos is sysVectorPolarOffset gSource (angle from ship to target) (min 150, distance from ship to target)
					alter newPos by 20ls using sysVectorRandom
			-->
			(block Nil
				(switch
					; If we have a target, jump.
					(objGetTarget gSource)
						(block (newPos)
							(setq newPos (sysVectorPolarOffset gSource (sysVectorAngle (objGetPos (objGetTarget gSource)) (objGetPos gSource)) (min 150 (objGetDistance (objGetTarget gSource) gSource))))

							(objJumpTo gSource (sysVectorPolarOffset newPos (random 0 360) (random 10 20)))
						)

					; If we have no target, do not jump, but provide a message as to why.
					(block Nil
						(objSendMessage gSource Nil "No target selected.")
					)
				)
			
				; Item identified
				(itmSetKnown gItem)
			)
		</Invoke>

		<Events>

			;Controls how this device will affect AI ships
			<OnAIUpdate>
				(block Nil
					(switch
						; If we have a target, the target is a ship, and the distance to the target exceeds 40, jump.
						(objGetTarget gSource)
							(if (and (gr (objGetDistance (objGetTarget gSource) gSource) 40) (objIsShip (objGetTarget gSource)))
								(block (newPos)
									(setq newPos (sysVectorPolarOffset gSource (sysVectorAngle (objGetPos (objGetTarget gSource)) (objGetPos gSource)) (min 150 (objGetDistance (objGetTarget gSource) gSource))))

									(objJumpTo gSource (sysVectorPolarOffset newPos (random 0 360) (random 10 20)))
								)
							)

						; If we have no target, do not jump, but provide a message as to why.
						(block Nil
							
						)
					)
				)
			</OnAIUpdate>

		</Events>
	</ItemType>

NMS
Militia Commander
Militia Commander
Posts: 386
Joined: Tue Mar 05, 2013 8:26 am

Tue Jun 28, 2016 1:25 am

If you just want to limit it to every certain amount of time, store the tick on which it jumps on the object using (unvGetTick) and (objSetData). Check to see if the current tick is greater than the desired amount of time plus the tick when it was last used.

PM
Fleet Admiral
Fleet Admiral
Posts: 2235
Joined: Wed Sep 01, 2010 12:54 am

Tue Jun 28, 2016 1:58 am

API 30 from 1.7 alpha 2 will support 'capacitor' and 'temperature' as properties. (Not sure if they are for weapons only or any device.) Until then, we cannot access the current power in a capacitor.

powerToActivate is your capacitor's maximum capacity (for non-weapons).

The value of capacitorPowerUse is added to the capacitor each tick.

To get how many ticks it takes to charge from empty to full, divide capacitorPowerUse by powerToActivate. Divide the result by 30 for number of seconds.
Download and Play in 1.7 Beta...
Playership Drones v7 (Beta): Acquire and command almost any ship in Stars of the Pilgrim or Eternity Port.
Drake Technologies (Alpha): More hardware for combat in parts 1 and 2!
Star Castle Arcade: Relive classic arcade gaming in a new Transcendence adventure!
Godmode v3 (WIP): Dev/cheat tool compatible with D&O parts 1 or 2.

User avatar
JohnBWatson
Fleet Officer
Fleet Officer
Posts: 1355
Joined: Tue Aug 19, 2014 10:17 pm

Tue Jun 28, 2016 2:09 am

Exactly what I needed. I'll get to figuring out ObjSetData immediately.

User avatar
JohnBWatson
Fleet Officer
Fleet Officer
Posts: 1355
Joined: Tue Aug 19, 2014 10:17 pm

Tue Jun 28, 2016 3:39 am

Alright, I've given the final part a try, and hit a glitch that I can't quite pinpoint the cause of. In the below code, (itmGetData gItem "aiJumpTimer") is always null, meaning the timer aspect does not work. I believe this means that (itmSetData gItem "aiJumpTimer" (unvGetTick)) is not working to set the aiJumpTimer value, but I can't see why that would be.

Code: Select all

;Controls how this device will affect AI ships
			<OnAIUpdate>
				(block Nil
					(if (or (not (itmGetData gItem "aiJumpTimer")) (gr (subtract (unvGetTick) (itmGetData gItem "aiJumpTimer") ) 333))
						(block Nil
							(switch
								; If we have a target, the target is a ship, and the distance to the target exceeds 40, jump.
								(objGetTarget gSource)
									(if (and (gr (objGetDistance (objGetTarget gSource) gSource) 40) (objIsShip (objGetTarget gSource)))
										(block (newPos)
											(setq newPos (sysVectorPolarOffset gSource (sysVectorAngle (objGetPos (objGetTarget gSource)) (objGetPos gSource)) (min 150 (objGetDistance (objGetTarget gSource) gSource))))

											(objJumpTo gSource (sysVectorPolarOffset newPos (random 0 360) (random 10 20)))

											;Store when we last jumped
											(itmSetData gItem "aiJumpTimer" (unvGetTick))
										)
									)

								; If we have no target, do not jump.
								(block Nil
							
								)
							)
						)
					)
				)
			</OnAIUpdate>

NMS
Militia Commander
Militia Commander
Posts: 386
Joined: Tue Mar 05, 2013 8:26 am

Tue Jun 28, 2016 12:29 pm

Well you could use objSetData on the ship. But if you want to store the data on an item that already exists on a SpaceObject, I think you have to use objSetItemData.

PM
Fleet Admiral
Fleet Admiral
Posts: 2235
Joined: Wed Sep 01, 2010 12:54 am

Tue Jun 28, 2016 12:49 pm

Note that storing data on an item will cause it to not stack with others, and I am not sure if setting such data to nil during OnUninstall even fixes that.

If you can sell the item, then non-stacking items will be a seller's dream (non-stacking items defeat buying limits) and a buyer's nightmare (too many identical items aside from hidden data clog the item list).
Download and Play in 1.7 Beta...
Playership Drones v7 (Beta): Acquire and command almost any ship in Stars of the Pilgrim or Eternity Port.
Drake Technologies (Alpha): More hardware for combat in parts 1 and 2!
Star Castle Arcade: Relive classic arcade gaming in a new Transcendence adventure!
Godmode v3 (WIP): Dev/cheat tool compatible with D&O parts 1 or 2.

User avatar
JohnBWatson
Fleet Officer
Fleet Officer
Posts: 1355
Joined: Tue Aug 19, 2014 10:17 pm

Tue Jun 28, 2016 1:30 pm

Switched back to ObjGetData and ObjSetData and it started working. Could've sworn that I had it that way before, but I suppose I did more some (block Nil)s around. I wonder what those things do, anyways. I don't always need them, but sometimes they're the difference between perfect functionality and crashes.

Regardless, I hope to get the balance to a point where it's passable and release a beta later today.

NMS
Militia Commander
Militia Commander
Posts: 386
Joined: Tue Mar 05, 2013 8:26 am

Tue Jun 28, 2016 11:15 pm

JohnBWatson wrote:I did more [move?] some (block Nil)s around. I wonder what those things do, anyways. I don't always need them, but sometimes they're the difference between perfect functionality and crashes.
You're trying to write code in (a language based on) Lisp without understanding blocks!? They're really important. Lisp doesn't normally execute multiple commands in sequence, so if you want that to happen, you have to put them inside a block. The first argument is used to define local variables you can use only inside the block, so if you don't need to do that, the first argument should be nil.

See http://xelerus.de/index.php?s=functions&function=10.

User avatar
JohnBWatson
Fleet Officer
Fleet Officer
Posts: 1355
Joined: Tue Aug 19, 2014 10:17 pm

Wed Jun 29, 2016 1:24 am

So, what you're saying is that they preserve atomicity? That is important. I already understood the bit about local variables.

In any case, I've got the beta done, so if anyone wants to give it a go, it's over here.

NMS
Militia Commander
Militia Commander
Posts: 386
Joined: Tue Mar 05, 2013 8:26 am

Wed Jun 29, 2016 6:27 pm

JohnBWatson wrote:So, what you're saying is that they preserve atomicity?
If by that you mean that they allow a series of instructions to be passed as a single argument, then yes. But I don't think atomicity is the right term. In Lisp, it can mean the property of being an atom rather than a list or function call. And in database systems it can mean that a series of instructions are carried out without other processes being able to do anything in between them. But that isn't relevant when there are no concurrent processes.

User avatar
JohnBWatson
Fleet Officer
Fleet Officer
Posts: 1355
Joined: Tue Aug 19, 2014 10:17 pm

Wed Jun 29, 2016 6:37 pm

NMS wrote:
JohnBWatson wrote:So, what you're saying is that they preserve atomicity?
If by that you mean that they allow a series of instructions to be passed as a single argument, then yes. But I don't think atomicity is the right term. In Lisp, it can mean the property of being an atom rather than a list or function call. And in database systems it can mean that a series of instructions are carried out without other processes being able to do anything in between them. But that isn't relevant when there are no concurrent processes.
I think I'm lost there. I thought that the latter definition was what you had meant.

NMS
Militia Commander
Militia Commander
Posts: 386
Joined: Tue Mar 05, 2013 8:26 am

Wed Jun 29, 2016 7:11 pm

So, say you have a server with several computers that can access or change the data on it. One of them wants to make several changes to the data. It's OK if one of the others accesses the data before the changes are made or after they're all done, but it's bad if it accesses the data while the changes are being made and gets a mix of old and new data. If the server is designed to carry out the changes as an atomic operation, it prevents any other process (like a request from one of the other computers) from accessing the data while the changes are being made.

But all the TLisp code in Transcendence is run by the main game thread, so you never have to worry about something happening in the middle of a piece of code you've written.

Here's an example of why you need blocks. You want to do several things if some condition is true. You might think you could do this:

Code: Select all

(if (condition...)
    (instruction1...)
    (instruction2...)
    (instruction3...) ; This is bad!
)
But this isn't how (if) works. The second argument is what it does if the condition is non-nil and the third argument is what it does if the condition is nil. The rest of the arguments will be ignored.

So do this:

Code: Select all

(if (condition...)
    (block nil
        (instruction1...)
        (instruction2...)
        (instruction3...)
    )
)

User avatar
JohnBWatson
Fleet Officer
Fleet Officer
Posts: 1355
Joined: Tue Aug 19, 2014 10:17 pm

Thu Jun 30, 2016 1:10 am

Ah, it just groups up several instructions. I understand now, thanks.

NMS
Militia Commander
Militia Commander
Posts: 386
Joined: Tue Mar 05, 2013 8:26 am

Fri Jul 01, 2016 12:53 am

That code you asked about in the stream does look like it should work. It's got a few more blocks and switches than necessary. For simplicity and readability, I'd change it to something like this:

Code: Select all

			<OnAIUpdate>
				(block (target)
					; If we have a target, the target is a ship, and the distance to the target exceeds 40, jump.
					(if (and (gr (subtract (unvGetTick) (objGetData gSource "aiJumpTimer") ) 500)
							(setq target (objGetTarget gSource))
							(gr (objGetDistance target gSource) 40)
							(objIsShip target)
							)
						(block (newPos)
							(setq newPos (sysVectorPolarOffset
											gSource
											(sysVectorAngle (objGetPos target) (objGetPos gSource))
											(min 150 (objGetDistance target gSource))
											)
								)

							(objJumpTo gSource (sysVectorPolarOffset newPos (random 0 360) (random 10 20)))

							;Store when we last jumped
							(objSetData gSource "aiJumpTimer" (unvGetTick))
							)
						)
					)
			</OnAIUpdate>
- Nil is treated as zero in math operations, there's no need to check whether the data is nil unless you care whether the AI can use the item in the first 17 seconds after starting a new game.
- I put all the conditions together.
- I saved (objGetTarget gSource) to avoid having to call it over and over.
- I broke up some long lines for readability.

Post Reply