Jump to content
Madin

AI Devourer Tank Conversion reserve use?

Recommended Posts

So this again.

 

To recap, trying to get the AI to use this special power via the 'AISpecialPowerUpdate' module, will cause the game to crash!

 

I was wondering if any of the better LUA scripters had any ideas on how to script the conversion reserve power being used automatically when the Devourer is over tiberium? I think if that could be done, then I could get the AI to use it.

 

Also for world builder users, is it even possible to script the an AI devourer to use the conversion reserve ability? I know that the map scripts have far more control then the near useless 'AISpecialPowerUpdate' module, when it comes to getting the AI to use specific abilities in specific situations.

However if it was not possible to script the usage in world builder, then it might just be impossible to get an AI controlled Devourer to use the conversion reserve ability at all.

 

From my understanding of the 'TiberiumThiefSpecialAbility' you cannot simply add tiberium ammo to a unit, without getting it from tiberium (or I would just find a way to give the AI tiberium ammo if they are over tiberium, and maybe have some FX, so it looks like they are using the conversion ability).

 

Thanks for any help!

Share this post


Link to post

Have the tiberium crystals give a attribute mod through a aura or weapon nugget. That sets a modelcondtition/object status, evaluate that in the scriptedeventlist...on modelcon./objectstat. do function

 

function UseTibThiefSp (self,other)

If ObjectTestModelCondition(self, USER_2) =/=1

then ObjectDoSpecialPower(TibCrystal, ConversionbeamSP)

End

End

 

Something like that maybe?

Share this post


Link to post

Have the tiberium crystals give a attribute mod through a aura or weapon nugget. That sets a modelcondtition/object status, evaluate that in the scriptedeventlist...on modelcon./objectstat. do function

 

function UseTibThiefSp (self,other)

If ObjectTestModelCondition(self, USER_2) =/=1

then ObjectDoSpecialPower(TibCrystal, ConversionbeamSP)

End

End

 

Something like that maybe?

Thanks, sadly that does not work (does nothing).

Share this post


Link to post

Did you use correct syntax?

 

function UseTibThiefSp(self,other)
if ObjectTestModelCondition(self,"USER_2") then
ObjectDoSpecialPower(self,"ConversionbeamSP")
end
end

 

Look if "ConversionbeamSP" is the correct name according to your sp xml definition.

If you use a lua event nugget somehow then self is the victim and other the attacker if i remember correct.

Share this post


Link to post

Did you use correct syntax?

 

function UseTibThiefSp(self,other)

if ObjectTestModelCondition(self,"USER_2") then

ObjectDoSpecialPower(self,"ConversionbeamSP")

end

end

 

Look if "ConversionbeamSP" is the correct name according to your sp xml definition.

If you use a lua event nugget somehow then self is the victim and other the attacker if i remember correct.

Thanks I will try this out.

Share this post


Link to post

Nope, did not work.

The issue seems to be getting the 'ObjectDoSpecialPower' to target something (neutral, enemy or allied).

 

Using the 'ObjectDoSpecialPower' for things like the Orca pulse scan is obviously very simple.

Share this post


Link to post

Sorry I'm not familiar with the conversion reserve power? How does it work exactly? Then i can give you a proper solution maybe. If it is something weapon related then you could use the SpawnBehaviorModule which will be triggered by an upgrade. The Upgrade can be triggered via lua, equally as above but with ObjectGrantUpgrade(self,"upgradename"). The spawned attacker can have a lifetime for 1s and then replace itself via death ocl with a new object that tests the model condition for it's parent and deletes itself if false.

Share this post


Link to post

Sorry I'm not familiar with the conversion reserve power? How does it work exactly? Then i can give you a proper solution maybe. If it is something weapon related then you could use the SpawnBehaviorModule which will be triggered by an upgrade. The Upgrade can be triggered via lua, equally as above but with ObjectGrantUpgrade(self,"upgradename"). The spawned attacker can have a lifetime for 1s and then replace itself via death ocl with a new object that tests the model condition for it's parent and deletes itself if false.

The conversion reserve power is the ability that the Scrin Devourers (and Kane's Wrath Reaper Tripods) have to use tiberium to charge up their weapon http://cnc.wikia.com/wiki/Devourer_tank .

 

Here is a look at the Special power template:

<SpecialPowerTemplate id="SpecialPower_ConversionBeam" TargetType="OBJECT" MaxCastRange="100" ReloadTime="10s" Flags="NEEDS_OBJECT_FILTER TARGET_NEEDS_OBJECT_STATUS" RequiredTargetObjectStatus="DOES_CONTAIN_TIBERIUM" DisallowedTargetObjectStatus="IS_BEING_HARVESTED" NameOfVoiceNameToUseAsInitiateIntendToDoVoice="InitiateConversionBeam" WaypointModeTerminal="false">
<ObjectFilter Rule="ANY" Include="FS_MONEY_STORAGE TIBERIUM">

And the special ability update inside the Devourers GameObject code:

 <TiberiumThiefSpecialAbilityUpdate id="ModuleTag_TiberiumThiefSpecialAbilityUpdate" SpecialPowerTemplate="SpecialPower_ConversionBeam" UnpackTime="0.1s" UnpackSound="ALI_DevourerTank_ConversionBeamFire" PersistentPrepTime="10000s" StartAbilityRange="50" TiberiumStolenPerUpdate="10" DelayBetweenThefts="0.3s" TiberiumCapacity="100" ObjectStatusWhenNotEmpty="HAS_TIBERIUM_AMMO" ModelConditionWhenNotEmpty="USER_2" StartRechargeOnExit="true"/>

So this is mostly used on tiberium (a neutral target).

Share this post


Link to post

Ok here is a solution I've tested for the lua part and it's working:

 

lua code (tested and working, fine adjustment up to you)

ObjectTypeHashIndexTable={["2946044786"]="AlienDevourerTank",["1097793023"]="Reaper17DevourerTank"}

GetObj={}
function GetObj.Hash(object)
    return tostring(tonumber(strsub(ObjectTemplateName(object), 6,  13), 16))
end

function TestUnitControlledByAI(object)  
    if strfind(ObjectTeamName(object), "Skirmish") ~= nil or strfind(ObjectDescription(object), " AI") ~= nil or strfind(ObjectDescription(object), " KI") ~= nil or strfind(ObjectDescription(input), "Enemy") ~= nil then return true
    else return false end
end

function IsEntryInTable(table,element)
     if type(table)=="string" then table=getglobal(table) end
     for i=1,getn(table),1 do
         if table[i]==element then return true end
     end
     return false
 end

function RemoveTableElement(table,element)
  if type(table)=="string" then table=getglobal(table) end
  for i=1,getn(table),1 do
    if table[i]==element then
      tremove(table,i)
      return
    end
  end
end

OccupiedObjectsTable={}
OccupiedObjectsSetFreeCountTable={}

function UseTibThiefSp(self,other)
  if TestUnitControlledByAI(self) then                                   --test if the unit belongs to an ai player
   if ObjectTypeHashIndexTable[GetObj.Hash(self)] ~= nil then            --this is our self made object filter test cause the lua event nugget has no
    if not ObjectTestModelCondition(self,"USER_2") and not IsEntryInTable("OccupiedObjectsTable",self) then  
        tinsert(OccupiedObjectsTable,self)                               --save the object reference into a table and make it "occupied" to avoid an endless special power command loop
        OccupiedObjectsSetFreeCountTable[self]=90                        --how many times the tiberiumcrystals should attack again before this unit gets ai again and occupied status removed (test and adapt)
        ExecuteAction("UNIT_AI_TRANSFER",self,0)                         --we turn off the ai temporarily so that the unit can do its special power without interruption
        ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_NAMED",self,"SpecialPower_ConversionBeam",other)
    elseif OccupiedObjectsSetFreeCountTable[self]<=0 then
        ExecuteAction("UNIT_AI_TRANSFER",self,1)                         --reactivate ai
        RemoveTableElement("OccupiedObjectsTable",self)
        --ExecuteAction("NAMED_HUNT",self)                               --optional, to beginn attack as fast as possible again        
    else
        OccupiedObjectsSetFreeCountTable[self]=OccupiedObjectsSetFreeCountTable[self]-1
    end
  end
 end
end

function RemoveFromOccupiedTable(self)     
 if TestUnitControlledByAI(self) then    
    RemoveTableElement("OccupiedObjectsTable",self)
 end
end

scriptevents part:

        <ObjectStatusEvent Name="TiberiumAmmoEmpty">
	       <Conditions>-HAS_TIBERIUM_AMMO</Conditions>
	    </ObjectStatusEvent>
		
  	<EventList Name="DevourerFunctions" Inherit="BaseScriptFunctions">
                <EventHandler EventName="OnCreated" ScriptFunctionName="OnReaper17DevourerCreated" DebugSingleStep="false"/>
  		<EventHandler EventName="LuaEventNuggetForDevourer" ScriptFunctionName="UseTibThiefSp" DebugSingleStep="false"/>
  		<EventHandler EventName="TiberiumAmmoEmpty" ScriptFunctionName="RemoveFromOccupiedTable" DebugSingleStep="false"/>
  		<EventHandler EventName="OnDestroyed" ScriptFunctionName="RemoveFromOccupiedTable" DebugSingleStep="false"/>		
   	 </EventList>	
	 
  	<EventList Name="Reaper17DevourerFunctions" Inherit="BaseScriptFunctions">
  		<EventHandler EventName="OnCreated" ScriptFunctionName="OnReaper17DevourerCreated" DebugSingleStep="false"/>
  		<EventHandler EventName="LuaEventNuggetForDevourer" ScriptFunctionName="UseTibThiefSp" DebugSingleStep="false"/>
  		<EventHandler EventName="TiberiumAmmoEmpty" ScriptFunctionName="RemoveFromOccupiedTable" DebugSingleStep="false"/>
  		<EventHandler EventName="OnDestroyed" ScriptFunctionName="RemoveFromOccupiedTable" DebugSingleStep="false"/>		
   	</EventList>

 

xml part (not tested, but should work):

 

weapon fire behaviour for tiberiumcrytsal green and blue (just added one entry):

            <FireWeaponUpdate
                id="955F8DC3"
                ActiveWhenDisabled=""
                HeroModeTrigger="False"
                ChargingModeTrigger="False"
                AliveOnly="False">
                <FireWeaponNugget
                    WeaponName="MARVTiberiumFXWeapon"
                    FireDelay="0s"
                    FireInterval="0.4s"
                    OneShot="False" />
                <FireWeaponNugget
                    WeaponName="TiberiumCrystalWeapon"
                    FireDelay="3s"
                    FireInterval="0s"
                    OneShot="False" />
                <FireWeaponNugget
                    WeaponName="TiberiumInfestationCrystalWeapon"
                    FireDelay="1.25s"
                    FireInterval="0s"
                    OneShot="False" />
                <FireWeaponNugget
                    WeaponName="TriggerDevourerConversionAbilityWeapon"
                    FireDelay="3s"
                    FireInterval="0s"
                    OneShot="False" />
            </FireWeaponUpdate>

weapon:

	<WeaponTemplate
		id="TriggerDevourerConversionAbilityWeapon"
		Name="TriggerDevourerConversionAbilityWeapon"
		RadiusDamageAffects="ALLIES ENEMIES NEUTRALS NOT_SIMILAR" >
		<PreAttackDelay
			MinSeconds="1.0s"
			MaxSeconds="2.5s"
		/>
		<FiringDuration
			MinSeconds="0s"
			MaxSeconds="0s"
		/>
		<Nuggets>
			<LuaEventNugget 
				EventName="LuaEventNuggetForDevourer"
				Radius="20.0"
				SendToEnemies="true"
				SendToAllies="true"
				SendToNeutral="true">				
                        </LuaEventNugget>				
		</Nuggets>
	</WeaponTemplate>

I don't know if this is for KW or TW. In any case prove the AILuaEventsList in th AIUpdate entry for both units. In KW both devourers have the same entry:

		<AI>
			<AIUpdate
				id="ModuleTag_AI"
				AutoAcquireEnemiesWhenIdle="YES"
				AILuaEventsList="Reaper17DevourerFunctions">
				<UnitAITargetChooserData
					CanPickDynamicTargets="false"
					SympathyRange="25.0" />
			</AIUpdate>
		</AI>
Edited by Mjjstral

Share this post


Link to post

Thanks for your help! This code would be for C&C3.

 

Anyway, this is not working (at all).

 

I was wondering:

ObjectTypeHashIndexTable={["2946044786"]="AlienDevourerTank" <<- is the number generic, or specific to Kane's Wrath?

Since you have often worked with LUA, is there any code that you have changed inside your Tiberium crsytal xml? (other than adding weapons).

Would the fact that the AI is always moving when on tiberium effect the ability for it to use the special power?

 

Checked:

Devourer is using LUA, (I have the KW version inside C&C3, so I use LUA to hide the upgrade sub object).

Checked my scripted events and scripts.lua entries, they seem fine.

 

I am testing the code inside a skirmish environment (Back water brawl, 3 x Scrin hard AI, with cheap Devourers so they build a lot).

Share this post


Link to post

ObjectTypeHashIndexTable={["2946044786"]="AlienDevourerTank" <<- is the number generic, or specific to Kane's Wrath?

 

That number can indeed differ from KW. Is there also a StringHashTable.xml somewhere in the TW assets? There you can find it. It occurs to me that you don't necessarily need the filter actually, cause only the devourer has the appropriate LuaEvent that activates the function. You can just comment it i would say.

 

Since you have often worked with LUA, is there any code that you have changed inside your Tiberium crsytal xml? (other than adding weapons).

 

I didn't change the xml codes. I have methods to directly run and change lua code from a textfile next to my game window and execute it with a button live ingame. With that I artificially called the UseTibThiefSp(self,other) function with a reference of a tiberiumcrytal and from a devourer I spawned... If you really get stuck on the xml part I may also help you there if I have more time.

 

Would the fact that the AI is always moving when on tiberium effect the ability for it to use the special power?

 

Yes I would say that's the case. But you can try ObjectSetObjectStatus(self,"NO_COLLISIONS") if the moving is caused by the crowdedness of other units around.

 

 

One more helpfull thing: For an easy way of debugging the lua code: ExecuteAction("SHOW_MILITARY_CAPTION", "DEBUG_POINT_X", 5)

This shows the message "DEBUG_POINT_X" for 5 seconds. Put it between the lines in the lua code with ascending index x and see where it gets stuck in the UseTibThiefSp function.

With that you can also show the hash string with ExecuteAction("SHOW_MILITARY_CAPTION",GetObj.Hash(self), 30) and see if it matches.

 

If nothing works you can also try a damage nugget with 0.0000001 damage (or even 0?) and then use DamageIncoming (self, other, delay, amount) script event because it also provides the needed self and other objectsreferences. But then you need the lua filter again.

Edited by Mjjstral

Share this post


Link to post

The number is the same as basically every sage game uses the same hashing algorithm.

Share this post


Link to post

Hey Lauren thanks for your input. I was always wondering about the hash algo. BTW would be nice to contact you in skype again :) ...

Share this post


Link to post

 

       public static uint GetHash(string input)
        {
            if (input.Length == 0)
            {
                return 0u;
            }
            if (IsHexNumber(input))
            {
                return uint.Parse(input.Substring(2), NumberStyles.HexNumber);
            }
 
            byte[] buffer = new byte[input.Length];
            BufferWriter.WriteString(buffer, 0, input);
 
            uint hash;
            int numBlocks = buffer.Length >> 2;
            int extra = buffer.Length % 4;
 
            hash = (uint)buffer.Length;
            int idy = 0;
            for (int idx = numBlocks; idx != 0; --idx)
            {
                hash += (uint)(buffer[idy + 1] << 8 | buffer[idy]);
                hash ^= ((uint)(buffer[idy + 3] << 8 | buffer[idy + 2]) ^ (hash << 5)) << 11;
                hash += hash >> 11;
                idy += 4;
            }
            if (extra != 0)
            {
                switch (extra)
                {
                    case 1:
                        hash += buffer[idy];
                        hash = (hash << 10) ^ hash;
                        hash += hash >> 1;
                        break;
                    case 2:
                        hash += (uint)(buffer[idy + 1] << 8 | buffer[idy]);
                        hash ^= hash << 11;
                        hash += hash >> 17;
                        break;
                    case 3:
                        hash += (uint)(buffer[idy + 1] << 8 | buffer[idy]);
                        hash ^= (hash ^ (uint)(buffer[idy + 2] << 2)) << 16;
                        hash += hash >> 11;
                        break;
                }
            }
            hash ^= hash << 3;
            hash += hash >> 5;
            hash ^= hash << 2;
            hash += hash >> 15;
            hash ^= hash << 10;
 
            return hash;
        }

Share this post


Link to post

Wow, thank you so much Lauren. I will try to convert that to lua.

Share this post


Link to post

This seems determined not to work.

 

As far as I can tell tiberium cannot send lua broadcast (I am solely referring to skirmish environment).

 

I tried both the lua broadcast weapon, and using the 'DamageIncoming' script, neither is received by the Devourer.

It is not your code, because I had a simple 'ObjectGrantUpgrade' test to see if even a basic script would work, nothing happened.

I know that in the case of the weapon I added to attempt to trigger the 'DamageIncoming' event, it was working because I was getting 'under attack' EVA events.

 

I tried forcing lua registration ( ForceLuaRegistration = true ), and adding a simple 'AI' element in the tiberium crystals 'GameObject', still no joy.

 

It seems to me that solutions that require tiberium to send a lua message, will not work.

Share this post


Link to post

It seems to me that solutions that require tiberium to send a lua message, will not work.

 

Try bypassing that part then by having the crystal use a attribute aura that gives a status or model condition to your devourer, then trigger the rest of the lua code by a event condition for that status/model condition?

Share this post


Link to post

Try bypassing that part then by having the crystal use a attribute aura that gives a status or model condition to your devourer, then trigger the rest of the lua code by a event condition for that status/model condition?

I think it has something to do with allowing the script to target another object. You will not be able to get the Devourer to target an object with the special power using the method you suggested.

Share this post


Link to post

Ok I've tried to also test and make a working solution for the xml part. Unfortunately I came across a strange behaviour: It seems that even if you force lua registration, the game prohibits the tiberium crystals from registering in the lua globals table. So if you use the event OnDamaged the custom tiberium crystal weapon trigger the event but on the lua part you get a hidden error cause it tries to call the object reference for the attacker which is not present, therefore also anything within the function won't get executed. Now the only solution I can think of now involves again large lua code segments and I'm not sure if the effort is worth it. It would work like that 1. Create an "alternative" object reference list with all tiberium crystals on the map. 2. Test the distance of each crystal with the devourer and based on that decide which one to choose.... Most of my algorithms use binary search but I'm not sure about the performance impact by using that way, because lua code doesn't get processed parallel. If you still decide to use that way send me your skype or something for better communication, thanks.

Share this post


Link to post

Ok I've tried to also test and make a working solution for the xml part. Unfortunately I came across a strange behaviour: It seems that even if you force lua registration, the game prohibits the tiberium crystals from registering in the lua globals table. So if you use the event OnDamaged the custom tiberium crystal weapon trigger the event but on the lua part you get a hidden error cause it tries to call the object reference for the attacker which is not present, therefore also anything within the function won't get executed. Now the only solution I can think of now involves again large lua code segments and I'm not sure if the effort is worth it. It would work like that 1. Create an "alternative" object reference list with all tiberium crystals on the map. 2. Test the distance of each crystal with the devourer and based on that decide which one to choose.... Most of my algorithms use binary search but I'm not sure about the performance impact by using that way, because lua code doesn't get processed parallel. If you still decide to use that way send me your Skype or something for better communication, thanks.

Thanks for your efforts!

 

It would seem as if the trouble that it would take to get this to work is perhaps not worth the effort.

 

My alternative is to get the AI to use their Tiberium refinery to charge up their tiberium weapon. The only catch is that the Tiberium refinery must have tiberium in it ( ObjectStatus 'DOES_CONTAIN_TIBERIUM' ) in order for the tiberium ammo to be transferred.

Obviously since the Refinery can send LUA, I am hoping that I will have less issues.

 

My Skype id is in my profile.

Share this post


Link to post

I guess my issue is that the refinery only sends the message once when it gets the status 'DOES_CONTAIN_TIBERIUM', I need it to constantly send the signal when it has that status, and stop when it does not.

 

Scrip events:

		<ObjectStatusEvent Name="ContainsTiberium">
			<Conditions>+DOES_CONTAIN_TIBERIUM</Conditions>
		</ObjectStatusEvent>

	<EventList Name="AlienTiberiumTowerFunctions" Inherit="BaseScriptFunctions">
		<EventHandler EventName="ContainsTiberium"	ScriptFunctionName="OnAlienTiberiumTowerHasTiberium" DebugSingleStep="false"/>
		<!-- <EventHandler EventName="ContainsNoTiberium"	ScriptFunctionName="OnAlienTiberiumTowerHasNoTiberium" DebugSingleStep="false"/> -->
	</EventList>

Scrip Lua:

function OnAlienTiberiumTowerHasTiberium(self)
	ObjectBroadcastEventToAllies(self, "LuaEventNuggetForDevourer", 50)
	ExecuteAction("SHOW_MILITARY_CAPTION", "REFINERY_HAS_TIBERIUM", 2)
end

So, still no reaction from the Devourers!

Share this post


Link to post

So I can give you a solution to these kind of problems in general if you don't mind dealing with the Meta Mod framework (or at least copy the necessary parts from it): Use a ScriptTimer: SetScriptTimer(EveryNSec,Action,Loops)

whereby Action can be a function or string that gets executed every n seconds for Loops. If loops is -1 it means infinite loops. You can still delete an infinite loop script timer action manually with DeleteScriptTimerAction(Action).

For the refinery solution you coud make a table in which each refinery registers itself. That table gets processed by a function that will then be the ScriptTimerAction.

 

Since Meta Mod source and everything is available now I give you also a Meta Mod solution for our old approach with TiberiumCrystals:

 

function GetNearestTiberiumCrytalRef(DevourerObject)
    ExecuteAction("OBJECTLIST_ADDOBJECTTYPE", "TiberiumCrystalTypeList", "TiberiumCrystal")
    ExecuteAction("OBJECTLIST_ADDOBJECTTYPE", "TiberiumCrystalTypeList", "TiberiumCrystalBlue")
    local TiberiumCrystalRefTable = GetObjectTypeListForTeam(NeutralTeam,"TiberiumCrystalTypeList")
    local CrystalPos = {}
    local CrystalRef = ""
    local DevourerPos = GetObjectPosition(DevourerObject)
    local LastDistance,NewDistance=15000,15000
    for i=1,getn(TiberiumCrystalRefTable),1 do
        CrystalPos=GetObjectPosition(TiberiumCrystalRefTable[i]["ref"])
        NewDistance=sqrt((DevourerPos.x-CrystalPos.x)^2+(DevourerPos.y-CrystalPos.y)^2)
        if NewDistance<LastDistance then
            LastDistance=NewDistance
            CrystalRef=TiberiumCrystalRefTable[i]["ref"]
        end
    end
    return CrystalRef
end

You can use that for the UseTibThiefSp(self,other) function I posted earlier, whereby other=GetNearestTiberiumCrytalRef(self).

 

The crucial line eventually would then be this:

 

ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_NAMED",self,"SpecialPower_ConversionBeam",other)

 

If something again goes wrong with the triggering, then use a ScriptTimer or insert a line into the "PeriodicMasterCheck" fuction (this function gets executed every 10 seconds to test all kinds of things in Meta Mod including victory conditions for custom gamemodes).

Edited by Mjjstral

Share this post


Link to post

Thanks for this information. I will be dedicating some time to this soon, and also checking out your Mod SDK.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×