Madin 10 Posted December 27, 2015 (edited) It is time for part IV of the highly unsuccessful Prism forwarding series A review of notable past threads: Part 1: Nothing to see Part 2: http://web.archive.org/web/20140705150621/http://www.commandandconquer.com/forums/index.php/topic/22229-prism-forwarding-issues-part-ii/ This centred around a Lua based solution, in the end, issues with getting a Lua launched power to target something other than itself thwarted the attempt, Golan has since disappeared! Part 3: http://forums.revora.net/topic/89776-prism-forwarding-part-iii/ Why using the Beam cannon type forwarding does not work There are too many hard coded issues with the Beam cannons forwarding method. The path that the beam will take to the target is not predictable, and it is possible for it to cover larger distances then it should be able to, missing forwarding targets in order to do so. Basically it is messy code that just about works for its intended purpose (although this is the closest I have got to getting forwarding to work). Goal The goal is to get the most basic (ie RA3 style) Prism forwarding to work. That is; if a single Prism tower has a target and it is preparing to fire, if any Prism towers within a certain range do not have a target, they will fire a support beam at the tower with a target, that will increase the power of its attack. I would like to get this basic style forwarding to work, before I think about full Ra2 style forwarding (ie towers in range of supporting towers also firing support beams for the tower with the target). Here is some Lauren advice, although I am too simple minded to fully understand it: "rather do a weapon which has a lua event nugget and a special power nugget the lua event nugget tells other towers to support this one (causing them to fire on this one with a weapon which triggers the beam effect and an attribute mod), and the special power nugget executes a storeobjectsspecialpower (also grant a state to itself so the weapon changes) the other attrimods add bonus damage and the new weapon fires the actual weapon with a hitstoredobjectfilteryou might want to delay a bit and fire via a specialpower to ensure the attrimods are all gathered (I don't think the sp will break if the attrimods are still coming in, and if you give it a unpack which is equal to the time the other towers charge you can use the unpack animation for the actual weapon animation and let the weapon only draw the beam) I can make a diagram if this was too hard to follow XD" Thanks for any advice given, I believe we will get there in the end! Edited December 27, 2015 by Madin Share this post Link to post
Egozi44 27 Posted December 27, 2015 (edited) Biber Mirage tank that was imported to KW's simple mod also lack that option. Didn't tried to make it connect with other "beams" yet but will be glad to know how to do it as well p.s didn't knew there's a web that let you watch old sites o_o did you saved the thread files and upload it to webarchive? Edited December 27, 2015 by Egozi44 Share this post Link to post
Madin 10 Posted May 21, 2017 This is a bumping of this old issue. I feel that there is a lot more that has been done with LUA over the past year and a half, so I figured that it might worth getting some fresh input on this. Please check the opening post on this thread for some background information. This is a frustrating issue! Share this post Link to post
Mjjstral 25 Posted May 21, 2017 (edited) I'm very confident that this is solvable, even for large cascade forwardings. Golan's approach wasn't bad, it just needs some more fine tuning I guess. To decide which tower will support or NOT support you can use this in addition to Golan's code: Use this function to test the minimum distances between the towers: function GetObjectDistance(object1 ,object2) if object1==nil or object2==nil or not EvaluateCondition("NAMED_NOT_DESTROYED",object1) or not EvaluateCondition("NAMED_NOT_DESTROYED",object2) then return 0 end local v = {} v.GetDistance = function(lowerlimit,upperlimit) local mid=(upperlimit+lowerlimit)/2 if upperlimit-lowerlimit<=1 then return lowerlimit elseif EvaluateCondition("DISTANCE_BETWEEN_OBJ", %object1, %object2, CompareTable[">"], mid) then return %v.GetDistance(lowerlimit,mid) else return round(%v.GetDistance(mid,upperlimit)) end end return v.GetDistance(0,12870) --maxdiagsize=ceil(sqrt(2*(7500+2*800)^2))) end CompareTable = { LT=0, LE=1, EQ=2, GE=3, GT=4, NE=5, [">"]=0, [">="]=1, ["=="]=2, ["<="]=3, ["<"]=4, ["~="]=5 } function GetObj.String(object) if type(object) == "string" then return object else local _, count = gsub(ObjectDescription(object),"%(","") if count>1 then return strsub(ObjectDescription(object), strfind(ObjectDescription(object),"(",1,true)+1,strfind(ObjectDescription(object),")",1,true)-1) else local StrRef = "object" .. RandomString(5) ExecuteAction("SET_UNIT_REFERENCE", StrRef, object) return StrRef end end end function RandomString(l) if l < 1 then l=5 end local s = "" local ascii_zone = {} for i=1,l,1 do ascii_zone[1] = random(97, 122) ascii_zone[2] = random(65, 90) ascii_zone[3] = random(48, 57) s = s .. strchar(ascii_zone[random(3)]) end return ("_" .. s) end Then you can use something like that: if GetObjectDistance(GetObj.String(self),GetObj.String(supporter))<150 and ObjectCountNearbyEnemies(supporter,SupportAttackRadius)>0 and ObjectCountNearbyEnemies(self,AttackRadius)>0 then ... end If I have time I can look more detailed into it. You could also avoid broadcasting and iterate through a list of all tower objects. In worst case a coroutine that gets called every n seconds and does all testing and proper attack distribution is also viable instead of every tower having to get triggered its own lua event. Edited May 22, 2017 by Mjjstral 1 Share this post Link to post
Lauren 77 Posted May 21, 2017 You sure you want to do worst case 14 iterations of the compare for each object pair? I mean it's using a script, which takes a lot more time to execute than other code. Also > is greater, < is lesser. Your table states the opposite. Share this post Link to post
Madin 10 Posted May 22, 2017 Thanks for the reply! I will test this out on Tuesday. Share this post Link to post
Mjjstral 25 Posted May 23, 2017 Quote You sure you want to do worst case 14 iterations of the compare for each object pair? I mean it's using a script, which takes a lot more time to execute than other code. Right, just wanted to give a general solution first. If possible pure usage of EvaluateCondition("DISTANCE_BETWEEN_OBJ", object1, object2, 0, distance) should be a priority. Quote Also > is greater, < is lesser. Your table states the opposite. Thanks, never noticed. The functions using it all work well though, so no big deal. Share this post Link to post
Mjjstral 25 Posted May 23, 2017 (edited) Ok here is my working solution, performance shouldn`t be a problem, although these algorithms could be more optimized if needed. function CascadeForwarding(self) self=GetObj.Table(self) local MaximumSupportDistance = 13000 local TowerAttackRange = 375 --here attack range of nod obelisk local ObjectList = {} local DistanceTable = {} local SupportConnection={} --create object list of all support towers for k,v in globals() do if strfind(k,"ObjID") and GetObj.Hash(self)==GetObj.Hash(v) and self~=v and ObjectTeamName(self)==ObjectTeamName(v) then if EvaluateCondition("DISTANCE_BETWEEN_OBJ", GetObj.String(self),GetObj.String(v), CompareTable["<="], MaximumSupportDistance) and ObjectCountNearbyEnemies(v,TowerAttackRange)==0 then tinsert(ObjectList,v) end end end tinsert(ObjectList,self) --create 2d matrix with distances for i=1,getn(ObjectList),1 do DistanceTable[tostring(ObjectList[i])]={} end for i=1,getn(ObjectList),1 do for j=i,getn(ObjectList),1 do if i==j then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=0 else if DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] == nil then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=GetObjectDistance(GetObj.String(ObjectList[i]),GetObj.String(ObjectList[j])) DistanceTable[tostring(ObjectList[j])][tostring(ObjectList[i])]=DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] end end end end --sort object list by distance to main tower local SortHelper=function(Object1,Object2) if %DistanceTable[tostring(%self)][tostring(Object1)] < %DistanceTable[tostring(%self)][tostring(Object2)] then return true end end sort(ObjectList,SortHelper) --main part: distribute one tower for each tower to support local MainNodeTable = {} tinsert(MainNodeTable,self) for i=2,getn(ObjectList),1 do local MainNodeCriteria = true local NextMainNode --closest main node local NextMainNodeDistance = 13000 for k=1,getn(MainNodeTable),1 do if NextMainNodeDistance > DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] then NextMainNodeDistance = DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] NextMainNode = MainNodeTable[k] end end local ShortestLinkToNextMainNodeDistance = 13000 for j=3,getn(ObjectList),1 do if not i==j then local iNodeTojNodeDistance = DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] local iNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[i])] local jNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[j])] if jNodeToNextMainNodeDistance < iNodeToNextMainNodeDistance then if iNodeTojNodeDistance < iNodeToNextMainNodeDistance then MainNodeCriteria = false local LinkToNextMainNodeDistance = iNodeTojNodeDistance + jNodeToNextMainNodeDistance if ShortestLinkToNextMainNodeDistance > LinkToNextMainNodeDistance then SupportConnection[tostring(ObjectList[i])]=ObjectList[j] ShortestLinkToNextMainNodeDistance = LinkToNextMainNodeDistance end end end end end if MainNodeCriteria == true then tinsert(MainNodeTable,ObjectList[i]) SupportConnection[tostring(ObjectList[i])]=NextMainNode end end --fire cumulative attribute modifier weapon on main tower (self) for i=2,getn(ObjectList),1 do ObjectCreateAndFireTempWeapon(self,"PrismTowerSupportWeapon") end --fire special power to display visual support beams for i=2,getn(ObjectList),1 do ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_NAMED",ObjectList[i],"SpecialPower_PrismForwarding",SupportConnection[tostring(ObjectList[i])]) --ExecuteAction("NAMED_ATTACK_NAMED",ObjectList[i],SupportConnection[tostring(ObjectList[i])]) end end All necessary helper functions: GetObj={} function GetObj.String(object) if type(object) == "string" then return object else local _, count = gsub(ObjectDescription(object),"%(","") if count>1 then return strsub(ObjectDescription(object), strfind(ObjectDescription(object),"(",1,true)+1,strfind(ObjectDescription(object),")",1,true)-1) else local StrRef = "object" .. RandomString(5) ExecuteAction("SET_UNIT_REFERENCE", StrRef, object) return StrRef end end end function GetObj.Table(object) if type(object) == "table" then return object else for k,v in globals() do if strfind(k,"ObjID") ~= nil and strfind(ObjectDescription(v),object) then return rawget(globals(),k) end end return object end end function GetObj.Hash(object) return tostring(tonumber(strsub(ObjectTemplateName(GetObj.Table(object)), 6, 13), 16)) end CompareTable = { LT=0, LE=1, EQ=2, GE=3, GT=4, NE=5, ["<"]=0, ["<="]=1, ["=="]=2, [">="]=3, [">"]=4, ["~="]=5 } function GetObjectDistance(object1 ,object2, start_distance) if object1==nil or object2==nil or not EvaluateCondition("NAMED_NOT_DESTROYED",object1) or not EvaluateCondition("NAMED_NOT_DESTROYED",object2) then return 0 end object1=GetObj.String(object1) object2=GetObj.String(object2) local v = {} v.GetDistance = function(lowerlimit,upperlimit) local mid=(upperlimit+lowerlimit)/2 if upperlimit-lowerlimit<=1 then return lowerlimit elseif EvaluateCondition("DISTANCE_BETWEEN_OBJ", %object1, %object2, CompareTable["<"], mid) then return %v.GetDistance(lowerlimit,mid) else return round(%v.GetDistance(mid,upperlimit)) end end if start_distance then return v.GetDistance(0,tonumber(start_distance)) else return v.GetDistance(0,12870) end --maxdiagsize=ceil(sqrt(2*(7500+2*800)^2))) end function random(...) --overwritting lua native function for multiplayer compatibility local RandomNumber = function(a,b) return floor(a+((b-a)*GetRandomNumber())+0.5) end if getn(arg) == 0 then return floor(GetRandomNumber()+0.5) elseif getn(arg) == 1 then return RandomNumber(1,arg[1]) elseif getn(arg) == 2 then return RandomNumber(arg[1],arg[2]) else return arg[RandomNumber(1,getn(arg))] end end function RandomString(l) if l < 1 then l=5 end local s = "" local ascii_zone = {} for i=1,l,1 do ascii_zone[1] = random(97, 122) ascii_zone[2] = random(65, 90) ascii_zone[3] = random(48, 57) s = s .. strchar(ascii_zone[random(3)]) end return ("_" .. s) end To test in meta mod easily execute (button in meta command menu) tthe following with the LIVE_INPUT.lua in the MetaModIO folder: MainObelisk = spawn("NODObelisk") Then build your Nod Obelisk or any other unit able to attack around the main unit in any way, comment out the spawn code and execute: CascadeForwarding(MainObelisk) Hope that finally solves your problem in a decent way. It`s now up to you to adapt the code for your stuff. Edited May 24, 2017 by Mjjstral 1 Share this post Link to post
Madin 10 Posted May 23, 2017 In terms of me testing this in a C&C3 skirmish match, is the 'function CascadeForwarding(self)' a script that is to be broadcast when the main Obelisk gets a target and goes into 'PREATTACK_A'? It is quite a complex script (for me), so I am just trying to minimise my errors. Share this post Link to post
Mjjstral 25 Posted May 23, 2017 (edited) Quote In terms of me testing this in a C&C3 skirmish match, is the 'function CascadeForwarding(self)' a script that is to be broadcast when the main Obelisk gets a target and goes into 'PREATTACK_A'? The only thing needed is the attacking unit/prism tower/obelisk or whatever to use that function on attacking: <ModelConditionEvent Name="BeginPreAttackA"> <Conditions>+PREATTACK_A</Conditions> </ModelConditionEvent> <EventList Name="PrismTower_EventList" Inherit="BaseScriptFunctions"> <EventHandler EventName="BeginPreAttackA" ScriptFunctionName="CascadeForwarding" DebugSingleStep="false"/> </EventList> No more broadcasting to other towers needed. That function (CascadeForwarding) handles everything else for you conveniently. I will possibly edit my main post above in the next days and change some lines and add all necessary helper functions needed for completness. Quote It is quite a complex script (for me), so I am just trying to minimise my errors. It is indeed not a trivial script and also some more problems will possibly occur while setting everything up for the real prism tower. Just keep me updated and we will fix everything one by one. The things you need to change later for your `final product` is the ObjectHash, TowerAttackradius, MaximumSupportDistance and also adapt the content of the last two for loops according to your taste. For the hash you can actually set ObjectHash=tostring(tonumber(strsub(ObjectTemplateName(self), 6, 13), 16)) Also don`t forget to add the AttributeModifierWeapon and the special power (last two for loops). For the weapon and attribute modifier my idea was something like this maybe: <WeaponTemplate id="PrismTowerSupportWeapon" Name="PrismTowerSupportWeapon" RadiusDamageAffects="ALLIES" AttackRange="15000.0"> <Nuggets> <AttributeModifierNugget Radius="15000" PartitionFilterTestType="SPHERE" AttributeModifierName="AttributeModifier_PrismTowerSupport"> <SpecialObjectFilter Rule="ANY" Relationship="SAME_PLAYER"> </SpecialObjectFilter> </AttributeModifierNugget> </Nuggets> </WeaponTemplate> <AttributeModifier id="AttributeModifier_PrismTowerSupport" Category="NONE" StackingLimit="999" Duration="1.0s"> <Modifier Type="DAMAGE_MULT" Value="100%"/> </AttributeModifier> At least the duration needs adjustment and I^m not sure about the DAMAGE_MULT, as I^ve seen absolute as well as percent values. Edited May 23, 2017 by Mjjstral 1 Share this post Link to post
Madin 10 Posted May 23, 2017 Thanks for your help! My first skirmish test failed. Script: function CascadeForwarding(self) local MaximumSupportDistance = 10000 local TowerAttackRadius = 375 --here attack range of nod obelisk local ObjectHash = "3868904616" --"NODObelisk" local ObjectList = {} local DistanceTable = {} local SupportConnection={} --create list of all relevant objects for k,v in globals() do if strfind(k,"ObjID")~=nil and GetObj.Hash(v)==ObjectHash and v~=self and ObjectTeamName(self)==ObjectTeamName(v) then local Distance = GetObjectDistance(GetObj.String(self),GetObj.String(v)) if Distance <= MaximumSupportDistance and ObjectCountNearbyEnemies(v,TowerAttackRadius)==0 then tinsert(ObjectList,v) end end end LastSupportTowerCount=getn(ObjectList) tinsert(ObjectList,self) --create 2d matrix with distances for i=1,getn(ObjectList),1 do DistanceTable[tostring(ObjectList[i])]={} end for i=1,getn(ObjectList),1 do for j=i,getn(ObjectList),1 do if i==j then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=0 else if DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] == nil then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=GetObjectDistance(GetObj.String(ObjectList[i]),GetObj.String(ObjectList[j])) DistanceTable[tostring(ObjectList[j])][tostring(ObjectList[i])]=DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] end end end end --sort object list by distance to main tower local SortHelper=function(Object1,Object2) if %DistanceTable[tostring(%self)][tostring(Object1)] < %DistanceTable[tostring(%self)][tostring(Object2)] then return true end end sort(ObjectList,SortHelper) --main part: distribute one tower for each tower to support local MainNodeTable = {} --tinsert(MainNodeTable,{[1]=self,["n"]=1}) tinsert(MainNodeTable,self) for i=2,getn(ObjectList),1 do local MainNodeCriteria = true local NextMainNode --closest main node local NextMainNodeDistance = 13000 for k=1,getn(MainNodeTable),1 do if NextMainNodeDistance > DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] then NextMainNodeDistance = DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] NextMainNode = MainNodeTable[k] end end local ShortestLinkToNextMainNodeDistance = 13000 for j=3,getn(ObjectList),1 do if not i==j then local iNodeTojNodeDistance = DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] local iNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[i])] local jNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[j])] if jNodeToNextMainNodeDistance < iNodeToNextMainNodeDistance then if iNodeTojNodeDistance < iNodeToNextMainNodeDistance then MainNodeCriteria = false local LinkToNextMainNodeDistance = iNodeTojNodeDistance + jNodeToNextMainNodeDistance if ShortestLinkToNextMainNodeDistance > LinkToNextMainNodeDistance then SupportConnection[tostring(ObjectList[i])]=ObjectList[j] ShortestLinkToNextMainNodeDistance = LinkToNextMainNodeDistance end end end end end if MainNodeCriteria == true then tinsert(MainNodeTable,ObjectList[i]) SupportConnection[tostring(ObjectList[i])]=NextMainNode end end --execute for i=1,LastSupportTowerCount,1 do --fire attribute booster weapon on main tower (for each support tower one weapon): ObjectCreateAndFireTempWeapon(self,"PrismTowerSupportWeapon") --alternative via object: --ExecuteAction("CREATE_NAMED_ON_TEAM_AT_OBJECTTYPE_WITH_ORIENTATION",RandomString(5),ObjectTypeToSpawn,GetTeamName(self),self,0) end for i=2,getn(ObjectList),1 do --use direct attack for testing: -- ExecuteAction("NAMED_ATTACK_NAMED",ObjectList[i],SupportConnection[tostring(ObjectList[i])]) --else use non damaging special power weapon: ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_NAMED",ObjectList[i],"SpecialPower_PrismForwarding",SupportConnection[tostring(ObjectList[i])]) end end I am using the attribute weapon suggested. And a simple weapon fire special power: <SpecialPower id="ModuleTag_PrismForwardingSpecialPower" SpecialPowerTemplate="SpecialPower_PrismForwarding" UpdateModuleStartsAttack="true"></SpecialPower> <WeaponFireSpecialAbilityUpdate id="ModuleTag_PrismForwardingBeamUpdate" SpecialPowerTemplate="SpecialPower_PrismForwarding" SpecialWeapon="TestObeliskLaser" StartAbilityRange="99999.0" UnpackTime="1s" WhichSpecialWeapon="1"></WeaponFireSpecialAbilityUpdate> <SpecialPowerTemplate id="SpecialPower_PrismForwarding" Flags="NEEDS_OBJECT_FILTER" TargetType="OBJECT"> <ObjectFilter Relationship="SAME_PLAYER" Rule="NONE"> <IncludeThing>NODOBELISK</IncludeThing> </ObjectFilter> </SpecialPowerTemplate> The scriptevent is also setup. I am running the test on the Nod Obelisk to start with. Share this post Link to post
Mjjstral 25 Posted May 24, 2017 (edited) Ok copy the now updated code and also all the helper functions from my main post and try again. For lua error logs use the following function and insert it into your scripts.lua file: function _ALERT(message) ExecuteAction("SHOW_MILITARY_CAPTION", "\n\n\n\n" .. tostring(message) .. "\n", 20) local file = "" local filehandle = openfile("ErrorLog",'a') if filehandle ~= nil then write(filehandle,"\n" .. "ErrorLogToFile(" .. date() .. "): " .. message) else local filehandle = writeto(file) write(filehandle,"\n" .. "ErrorLogToFile(" .. date() .. "): " .. message) end flush(filehandle) closefile(filehandle) end It will save/refresh a file called "ErrorLog" which will be in the same folder where your "cnc3game.dat" is placed. Send the logs to me then if you can't solve it. Else there must be a xml coding flaw somewhere we missed. Here some pictures of some more tests, also in first person mode which looks epic with all the towers doing their action at once: Edited May 24, 2017 by Mjjstral 1 Share this post Link to post
Madin 10 Posted May 25, 2017 Nice! I now have error messages! I have attached it. Here is then code order I added comments for the error line numbers (although you probably do not need them): GetObj={} function GetObj.String(object) if type(object) == "string" then return object else local _, count = gsub(ObjectDescription(object),"%(","") if count>1 then return strsub(ObjectDescription(object), strfind(ObjectDescription(object),"(",1,true)+1,strfind(ObjectDescription(object),")",1,true)-1) else local StrRef = "object" .. RandomString(5) ExecuteAction("SET_UNIT_REFERENCE", StrRef, object) return StrRef end end end function GetObj.Table(object) if type(object) == "table" then return object else for k,v in globals() do if strfind(k,"ObjID") ~= nil and strfind(ObjectDescription(v),object) then return rawget(globals(),k) end end return object end end function GetObj.Hash(object) return tostring(tonumber(strsub(ObjectTemplateName(GetObj.Table(object)), 6, 13), 16)) end CompareTable = { LT=0, LE=1, EQ=2, GE=3, GT=4, NE=5, ["<"]=0, ["<="]=1, ["=="]=2, [">="]=3, [">"]=4, ["~="]=5 } function GetObjectDistance(object1 ,object2, start_distance) if object1==nil or object2==nil or not EvaluateCondition("NAMED_NOT_DESTROYED",object1) or not EvaluateCondition("NAMED_NOT_DESTROYED",object2) then return 0 end object1=GetObj.String(object1) object2=GetObj.String(object2) local v = {} v.GetDistance = function(lowerlimit,upperlimit) local mid=(upperlimit+lowerlimit)/2 if upperlimit-lowerlimit<=1 then return lowerlimit elseif EvaluateCondition("DISTANCE_BETWEEN_OBJ", %object1, %object2, CompareTable["<"], mid) then return --Error (Line 45) else return round(%v.GetDistance(mid,upperlimit)) end --Error (Line 46) end if start_distance then return v.GetDistance(0,tonumber(start_distance)) else return v.GetDistance(0,12870) end --maxdiagsize=ceil(sqrt(2*(7500+2*800)^2))) --Error (Line 49) end function random(...) --overwritting lua native function for multiplayer compatibility local RandomNumber = function(a,b) return floor(a+((b-a)*GetRandomNumber())+0.5) end if getn(arg) == 0 then return floor(GetRandomNumber()+0.5) elseif getn(arg) == 1 then return RandomNumber(1,arg[1]) elseif getn(arg) == 2 then return RandomNumber(arg[1],arg[2]) else return arg[RandomNumber(1,getn(arg))] end end function RandomString(l) if l < 1 then l=5 end local s = "" local ascii_zone = {} for i=1,l,1 do ascii_zone[1] = random(97, 122) ascii_zone[2] = random(65, 90) ascii_zone[3] = random(48, 57) s = s .. strchar(ascii_zone[random(3)]) end return ("_" .. s) end function CascadeForwarding(self) self=GetObj.Table(self) local MaximumSupportDistance = 13000 local TowerAttackRange = 375 --here attack range of nod obelisk local ObjectList = {} local DistanceTable = {} local SupportConnection={} --create object list of all support towers for k,v in globals() do if strfind(k,"ObjID") and GetObj.Hash(self)==GetObj.Hash(v) and self~=v and ObjectTeamName(self)==ObjectTeamName(v) then if EvaluateCondition("DISTANCE_BETWEEN_OBJ", GetObj.String(self),GetObj.String(v), CompareTable["<="], MaximumSupportDistance) and ObjectCountNearbyEnemies(v,TowerAttackRange)==0 then tinsert(ObjectList,v) end end end tinsert(ObjectList,self) --create 2d matrix with distances for i=1,getn(ObjectList),1 do DistanceTable[tostring(ObjectList[i])]={} end for i=1,getn(ObjectList),1 do for j=i,getn(ObjectList),1 do if i==j then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=0 else if DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] == nil then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=GetObjectDistance(GetObj.String(ObjectList[i]),GetObj.String(ObjectList[j])) --Error (Line 99) DistanceTable[tostring(ObjectList[j])][tostring(ObjectList[i])]=DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] end end end end --sort object list by distance to main tower local SortHelper=function(Object1,Object2) if %DistanceTable[tostring(%self)][tostring(Object1)] < %DistanceTable[tostring(%self)][tostring(Object2)] then return true end end sort(ObjectList,SortHelper) --main part: distribute one tower for each tower to support local MainNodeTable = {} tinsert(MainNodeTable,self) for i=2,getn(ObjectList),1 do local MainNodeCriteria = true local NextMainNode --closest main node local NextMainNodeDistance = 13000 for k=1,getn(MainNodeTable),1 do if NextMainNodeDistance > DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] then NextMainNodeDistance = DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] NextMainNode = MainNodeTable[k] end end local ShortestLinkToNextMainNodeDistance = 13000 for j=3,getn(ObjectList),1 do if not i==j then local iNodeTojNodeDistance = DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] local iNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[i])] local jNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[j])] if jNodeToNextMainNodeDistance < iNodeToNextMainNodeDistance then if iNodeTojNodeDistance < iNodeToNextMainNodeDistance then MainNodeCriteria = false local LinkToNextMainNodeDistance = iNodeTojNodeDistance + jNodeToNextMainNodeDistance if ShortestLinkToNextMainNodeDistance > LinkToNextMainNodeDistance then SupportConnection[tostring(ObjectList[i])]=ObjectList[j] ShortestLinkToNextMainNodeDistance = LinkToNextMainNodeDistance end end end end end if MainNodeCriteria == true then tinsert(MainNodeTable,ObjectList[i]) SupportConnection[tostring(ObjectList[i])]=NextMainNode end end --fire cumulative attribute modifier weapon on main tower (self) for i=2,getn(ObjectList),1 do ObjectCreateAndFireTempWeapon(self,"PrismTowerSupportWeapon") end --fire special power to display visual support beams for i=2,getn(ObjectList),1 do ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_NAMED",ObjectList[i],"SpecialPower_PrismForwarding",SupportConnection[tostring(ObjectList[i])]) --ExecuteAction("NAMED_ATTACK_NAMED",ObjectList[i],SupportConnection[tostring(ObjectList[i])]) end end function _ALERT(message) ExecuteAction("SHOW_MILITARY_CAPTION", "\n\n\n\n" .. tostring(message) .. "\n", 20) local file = "" local filehandle = openfile("ErrorLog",'a') if filehandle ~= nil then write(filehandle,"\n" .. "ErrorLogToFile(" .. date() .. "): " .. message) else local filehandle = writeto(file) write(filehandle,"\n" .. "ErrorLogToFile(" .. date() .. "): " .. message) end flush(filehandle) closefile(filehandle) end Thanks! ErrorLog.txt Share this post Link to post
Mjjstral 25 Posted May 26, 2017 Oh sorry, that one function was missing. Simple math round: Quote function round(number) return floor(number+0.5) end Let's see what comes next ;). 1 Share this post Link to post
Madin 10 Posted May 26, 2017 (edited) That code certainly cleared up the errors, thanks! Now I can onto the fact that this dam thing won't work... fully. When I try using the Special power for the visual link, it does not work. So I switched to the 'NAMED_ATTACK_NAMED' method, and removed the ability for the Obelisk weapon to damage other Obelisk. On testing this, the visual link works, but the issue is that the Obelisk will all fire support beams at each other, and ignore enemy targets! Code: GetObj={} function GetObj.String(object) if type(object) == "string" then return object else local _, count = gsub(ObjectDescription(object),"%(","") if count>1 then return strsub(ObjectDescription(object), strfind(ObjectDescription(object),"(",1,true)+1,strfind(ObjectDescription(object),")",1,true)-1) else local StrRef = "object" .. RandomString(5) ExecuteAction("SET_UNIT_REFERENCE", StrRef, object) return StrRef end end end function GetObj.Table(object) if type(object) == "table" then return object else for k,v in globals() do if strfind(k,"ObjID") ~= nil and strfind(ObjectDescription(v),object) then return rawget(globals(),k) end end return object end end function GetObj.Hash(object) return tostring(tonumber(strsub(ObjectTemplateName(GetObj.Table(object)), 6, 13), 16)) end CompareTable = { LT=0, LE=1, EQ=2, GE=3, GT=4, NE=5, ["<"]=0, ["<="]=1, ["=="]=2, [">="]=3, [">"]=4, ["~="]=5 } function GetObjectDistance(object1 ,object2, start_distance) if object1==nil or object2==nil or not EvaluateCondition("NAMED_NOT_DESTROYED",object1) or not EvaluateCondition("NAMED_NOT_DESTROYED",object2) then return 0 end object1=GetObj.String(object1) object2=GetObj.String(object2) local v = {} v.GetDistance = function(lowerlimit,upperlimit) local mid=(upperlimit+lowerlimit)/2 if upperlimit-lowerlimit<=1 then return lowerlimit elseif EvaluateCondition("DISTANCE_BETWEEN_OBJ", %object1, %object2, CompareTable["<"], mid) then return %v.GetDistance(lowerlimit,mid) else return round(%v.GetDistance(mid,upperlimit)) end end if start_distance then return v.GetDistance(0,tonumber(start_distance)) else return v.GetDistance(0,12870) end --maxdiagsize=ceil(sqrt(2*(7500+2*800)^2))) end function random(...) --overwritting lua native function for multiplayer compatibility local RandomNumber = function(a,b) return floor(a+((b-a)*GetRandomNumber())+0.5) end if getn(arg) == 0 then return floor(GetRandomNumber()+0.5) elseif getn(arg) == 1 then return RandomNumber(1,arg[1]) elseif getn(arg) == 2 then return RandomNumber(arg[1],arg[2]) else return arg[RandomNumber(1,getn(arg))] end end function RandomString(l) if l < 1 then l=5 end local s = "" local ascii_zone = {} for i=1,l,1 do ascii_zone[1] = random(97, 122) ascii_zone[2] = random(65, 90) ascii_zone[3] = random(48, 57) s = s .. strchar(ascii_zone[random(3)]) end return ("_" .. s) end function CascadeForwarding(self) self=GetObj.Table(self) local MaximumSupportDistance = 13000 local TowerAttackRange = 375 --here attack range of nod obelisk local ObjectList = {} local DistanceTable = {} local SupportConnection={} --create object list of all support towers for k,v in globals() do if strfind(k,"ObjID") and GetObj.Hash(self)==GetObj.Hash(v) and self~=v and ObjectTeamName(self)==ObjectTeamName(v) then if EvaluateCondition("DISTANCE_BETWEEN_OBJ", GetObj.String(self),GetObj.String(v), CompareTable["<="], MaximumSupportDistance) and ObjectCountNearbyEnemies(v,TowerAttackRange)==0 then tinsert(ObjectList,v) end end end tinsert(ObjectList,self) --create 2d matrix with distances for i=1,getn(ObjectList),1 do DistanceTable[tostring(ObjectList[i])]={} end for i=1,getn(ObjectList),1 do for j=i,getn(ObjectList),1 do if i==j then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=0 else if DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] == nil then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=GetObjectDistance(GetObj.String(ObjectList[i]),GetObj.String(ObjectList[j])) DistanceTable[tostring(ObjectList[j])][tostring(ObjectList[i])]=DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] end end end end --sort object list by distance to main tower local SortHelper=function(Object1,Object2) if %DistanceTable[tostring(%self)][tostring(Object1)] < %DistanceTable[tostring(%self)][tostring(Object2)] then return true end end sort(ObjectList,SortHelper) --main part: distribute one tower for each tower to support local MainNodeTable = {} tinsert(MainNodeTable,self) for i=2,getn(ObjectList),1 do local MainNodeCriteria = true local NextMainNode --closest main node local NextMainNodeDistance = 13000 for k=1,getn(MainNodeTable),1 do if NextMainNodeDistance > DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] then NextMainNodeDistance = DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] NextMainNode = MainNodeTable[k] end end local ShortestLinkToNextMainNodeDistance = 13000 for j=3,getn(ObjectList),1 do if not i==j then local iNodeTojNodeDistance = DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] local iNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[i])] local jNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[j])] if jNodeToNextMainNodeDistance < iNodeToNextMainNodeDistance then if iNodeTojNodeDistance < iNodeToNextMainNodeDistance then MainNodeCriteria = false local LinkToNextMainNodeDistance = iNodeTojNodeDistance + jNodeToNextMainNodeDistance if ShortestLinkToNextMainNodeDistance > LinkToNextMainNodeDistance then SupportConnection[tostring(ObjectList[i])]=ObjectList[j] ShortestLinkToNextMainNodeDistance = LinkToNextMainNodeDistance end end end end end if MainNodeCriteria == true then tinsert(MainNodeTable,ObjectList[i]) SupportConnection[tostring(ObjectList[i])]=NextMainNode end end --fire cumulative attribute modifier weapon on main tower (self) for i=2,getn(ObjectList),1 do ObjectCreateAndFireTempWeapon(self,"PrismTowerSupportWeapon") end --fire special power to display visual support beams for i=2,getn(ObjectList),1 do -- ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_NAMED",ObjectList[i],"SpecialPower_PrismForwarding",SupportConnection[tostring(ObjectList[i])]) ExecuteAction("NAMED_ATTACK_NAMED",ObjectList[i],SupportConnection[tostring(ObjectList[i])]) end end function round(number) return floor(number+0.5) end function _ALERT(message) ExecuteAction("SHOW_MILITARY_CAPTION", "\n\n\n\n" .. tostring(message) .. "\n", 20) local file = "" local filehandle = openfile("ErrorLog",'a') if filehandle ~= nil then write(filehandle,"\n" .. "ErrorLogToFile(" .. date() .. "): " .. message) else local filehandle = writeto(file) write(filehandle,"\n" .. "ErrorLogToFile(" .. date() .. "): " .. message) end flush(filehandle) closefile(filehandle) end Thanks! Edited May 26, 2017 by Madin Added code Share this post Link to post
Mjjstral 25 Posted May 27, 2017 (edited) Hey I've changed quite alot, so that towers that have enemies in attack range and do not have "attack on position" commands issued won't support other tower anymore. That means you can now test the system with attack on position commands without enemies even. The problems: I also didn't succeed to make it work via a special power on the xml coding side. My advice is to create that special power as a button ability and make sure it is actually working via manual use. Then it will surely work on the lua side. There was something similiar with the devourer conversion reserve use we worked on, where the special power use on another object also worked. My alternative approach is to still use ExecuteAction("NAMED_ATTACK_NAMED",...) , but with a tertiary non damaging weapon that only can be used on the respective tower object (here NodObelisk). The very strange thing for me was, that every second support beam, does damage to the tower they support. This can probably get solved by reissueing the attack command precisely after each attack. Last problem: The attribute modifier didn't work for me at all, there's something missing that you need to find. I've used attribute modifiers with ObjectCreateAndFireTempWeapon before, so in theory it is viable. If it has to do with the weapon with it's damage attribute already locked after EventName="PREATTACK_A" then you can use EventName="ATTACKING" which gets triggered before. This gets more and more complicated each day xD So here is all the code: MainTowerSupporterList= {} MainTowerSupportConnectionList= {} function CascadeForwarding(self) --made for "Madin" (cncnz forums) self=GetObj.Table(self) local IsObjectInSupporterList = function(object) for q,p in MainTowerSupporterList do --to test if a tower already supports another tower if q~=tostring(%self) then for i=1,getn(p),1 do if p[i]==object then return true end end end end return false end local MaximumSupportDistance = 13000 local TowerAttackRange = 375 --here attack range of nod obelisk if ObjectCountNearbyEnemies(self,TowerAttackRange)==0 and IsObjectInSupporterList(self) then return end local ObjectList = {} local DistanceTable = {} local SupportConnection={} --create object list of all support towers for k,v in globals() do if strfind(k,"ObjID") and GetObj.Hash(self)==GetObj.Hash(v) and self~=v and ObjectTeamName(self)==ObjectTeamName(v) then if ObjectCountNearbyEnemies(v,TowerAttackRange)==0 and EvaluateCondition("DISTANCE_BETWEEN_OBJ", GetObj.String(self),GetObj.String(v), CompareTable["<="], MaximumSupportDistance) then if not IsObjectInSupporterList(v) then tinsert(ObjectList,v) end end end end tinsert(ObjectList,self) if getn(ObjectList)<2 then return end --create 2d matrix with distances for i=1,getn(ObjectList),1 do DistanceTable[tostring(ObjectList[i])]={} end for i=1,getn(ObjectList),1 do for j=i,getn(ObjectList),1 do if i==j then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=0 else if DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] == nil then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=GetObjectDistance(GetObj.String(ObjectList[i]),GetObj.String(ObjectList[j])) DistanceTable[tostring(ObjectList[j])][tostring(ObjectList[i])]=DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] end end end end --sort object list by distance to main tower local SortHelper=function(Object1,Object2) if %DistanceTable[tostring(%self)][tostring(Object1)] < %DistanceTable[tostring(%self)][tostring(Object2)] then return true end end sort(ObjectList,SortHelper) --main part: distribute one tower for each tower to support local MainNodeTable = {} tinsert(MainNodeTable,self) local i = 2 local ObjectListSize for i=2,getn(ObjectList),1 do local MainNodeCriteria = true local NextMainNode, NextMainNodeAlternative --closest main node local NextMainNodeDistance = 13000 for k=1,getn(MainNodeTable),1 do if NextMainNodeDistance > DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] then NextMainNodeDistance = DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] NextMainNode = MainNodeTable[k] end end local ShortestLinkToNextMainNodeDistance = 13000 for j=3,getn(ObjectList),1 do if not i==j and ObjectList[j]~=nil then local iNodeTojNodeDistance = DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] local iNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[i])] local jNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[j])] if jNodeToNextMainNodeDistance < iNodeToNextMainNodeDistance then if iNodeTojNodeDistance < iNodeToNextMainNodeDistance then MainNodeCriteria = false local LinkToNextMainNodeDistance = iNodeTojNodeDistance + jNodeToNextMainNodeDistance if ShortestLinkToNextMainNodeDistance > LinkToNextMainNodeDistance then NextMainNodeAlternative=ObjectList[j] ShortestLinkToNextMainNodeDistance = LinkToNextMainNodeDistance end end end end end if MainNodeCriteria == true then if TowerAttackRange > DistanceTable[tostring(NextMainNode)][tostring(ObjectList[i])] then tinsert(MainNodeTable,ObjectList[i]) SupportConnection[tostring(ObjectList[i])]=NextMainNode else ObjectList[i]=nil end else if TowerAttackRange > ShortestLinkToNextMainNodeDistance then SupportConnection[tostring(ObjectList[i])]=NextMainNodeAlternative else ObjectList[i]=nil end end end --remove all unneccasary towers local CleanedObjectList = {} for i=1,getn(ObjectList),1 do if not ObjectList[i]==nil then tinsert(CleanedObjectList,ObjectList[i]) end end ObjectList=CleanedObjectList MainTowerSupporterList[tostring(self)] = CleanedObjectList MainTowerSupportConnectionList[tostring(self)]=SupportConnection --fire cumulative attribute modifier weapon on main tower (self) for i=2,getn(ObjectList),1 do ObjectCreateAndFireTempWeapon(self,"PrismTowerSupportWeapon") end --fire special power to display visual support beams for i=2,getn(ObjectList),1 do --ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_NAMED",ObjectList[i],"SpecialPower_PrismForwarding",SupportConnection[tostring(ObjectList[i])]) ExecuteAction("NAMED_STOP",ObjectList[i]) ExecuteAction("NAMED_ATTACK_NAMED",ObjectList[i],SupportConnection[tostring(ObjectList[i])]) end end And here are the additional functions: function GetMyMainTower(object) for q,p in MainTowerSupporterList do if q~=tostring(object) then for i=1,getn(p),1 do if p[i]==object then return p[1] end end end end end function IsTowerStillAttacking(MainTower) if ObjectTestModelCondition(MainTower,"ATTACKING") or ObjectTestModelCondition(MainTower,"PREATTACK_A") or ObjectTestModelCondition(MainTower,"RELOADING_A") or ObjectTestModelCondition(MainTower,"FIRING_A") then return true else return false end end function NodObelisk_RELOADING(self) local TowerAttackRange = 375 if MainTowerSupporterList[tostring(self)]~=nil then --and ObjectCountNearbyEnemies(self,TowerAttackRange)==0 then for i=2,getn(MainTowerSupporterList[tostring(self)]),1 do ExecuteAction("NAMED_STOP",MainTowerSupporterList[tostring(self)][i]) end --MainTowerSupporterList[tostring(self)]=nil else if ObjectCountNearbyEnemies(self,TowerAttackRange)==0 then ExecuteAction("NAMED_STOP",self) end local MainTower = GetMyMainTower(self) if not IsTowerStillAttacking(MainTower) then MainTowerSupporterList[tostring(MainTower)]=nil else ExecuteAction("NAMED_STOP",self) ExecuteAction("NAMED_ATTACK_NAMED",self,MainTowerSupportConnectionList[tostring(MainTower)][tostring(self)]) end end end function getn2(table) local size = 0 for k,v in table do size=size+1 end return size end And for the scriptevents.xml: <EventList Name="NodObeliskEvents"> <!-- <EventHandler EventName="ATTACKING" ScriptFunctionName="CascadeForwarding" DebugSingleStep="false"/> --> <EventHandler EventName="PREATTACK_A" ScriptFunctionName="CascadeForwarding" DebugSingleStep="false"/> <!-- <EventHandler EventName="PREATTACK_B" ScriptFunctionName="CascadeForwarding" DebugSingleStep="false"/> --> <!-- <EventHandler EventName="FIRING_A" ScriptFunctionName="NodObelisk_FIRING_A" DebugSingleStep="false"/> --> <EventHandler EventName="RELOADING_A" ScriptFunctionName="NodObelisk_RELOADING" DebugSingleStep="false"/> <EventHandler EventName="RELOADING_C" ScriptFunctionName="NodObelisk_RELOADING" DebugSingleStep="false"/> </EventList> <ModelConditionEvent Name="PREATTACK_A"> <Conditions>+PREATTACK_A</Conditions> </ModelConditionEvent> <ModelConditionEvent Name="PREATTACK_B"> <Conditions>+PREATTACK_B</Conditions> </ModelConditionEvent> <ModelConditionEvent Name="FIRING_A"> <Conditions>+FIRING_A</Conditions> </ModelConditionEvent> <ModelConditionEvent Name="RELOADING_A"> <Conditions>+RELOADING_A</Conditions> </ModelConditionEvent> <ModelConditionEvent Name="RELOADING_C"> <Conditions>+RELOADING_C</Conditions> </ModelConditionEvent> <ModelConditionEvent Name="ATTACKING"> <Conditions>+ATTACKING</Conditions> </ModelConditionEvent> And here is the NodObelisk object and it's weapons and stuff, I've used: <?xml version="1.0" encoding="utf-8"?> <AssetDeclaration xmlns="uri:ea.com:eala:asset" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xi="http://www.w3.org/2001/XInclude"> <Tags></Tags> <Includes> <Include type="all" source="ART:NBObelisk.w3x" /> <Include type="all" source="ART:NBObeliskA_SN.w3x" /> <Include type="all" source="ART:NBObeliskA_AN.w3x" /> <Include type="all" source="ART:NBObeliskD2_SN.w3x" /> <Include type="all" source="ART:NBObeliskD2_AN.w3x" /> <Include type="all" source="ART:NBObeliskD3_SN.w3x" /> <Include type="all" source="ART:NBObeliskD3_AN.w3x" /> <Include type="all" source="ART:FXObeliskLaser.xml" /> <Include type="all" source="ART:FXLaserOverlay.xml" /> <Include type="all" source="ART:NBObeliskCrstl.w3x" /> <Include type="all" source="DATA:GlobalData/GlobalDefines.xml" /> <Include type="all" source="ART:FXspotlight.w3x" /> <!-- Base Object --> <Include type="instance" source="DATA:BaseObjects/BaseStructure.xml" /> </Includes> <GameObject id="NODObelisk" inheritFrom="BaseStructure" SelectPortrait="Portrait_NODObeliskofLight" ButtonImage="Portrait_NODObeliskofLight" Side="NOD" EditorSorting="STRUCTURE" TransportSlotCount="1" BuildCost="2000" BuildTime="20" EnergyProduction="-15" CommandSet="NODObeliskCommandSet" KindOf="STRUCTURE SELECTABLE IMMOBILE CAN_CAST_REFLECTIONS FS_BASE_DEFENSE POWERED AUTO_ACQUIRABLE_BY_AI COVER CAN_ATTACK CAN_SEE_THROUGH_STRUCTURE ATTACK_NEEDS_LINE_OF_SIGHT LINE_OF_SIGHT_IGNORES_BUILDINGS" RadarPriority="STRUCTURE" PlacementViewAngle="315d" ProductionQueueType="OTHER_STRUCTURE" BuildPlacementTypeFlag="OTHER_STRUCTURE" EditorName="NODObelisk" WeaponCategory="CANNON" TypeDescription="Type:NODObelisk" Description="Desc:NODObelisk"> <DisplayName>Name:NODObelisk</DisplayName> <GameDependency> <RequiredObject>NODTechAssemblyPlant</RequiredObject> </GameDependency> <ArmorSet Armor="NodObeliskArmor" DamageFX="FactionStructureDamageFX" /> <SkirmishAIInformation BaseBuildingLocation="SPREAD" /> <Draws> <ScriptedModelDraw id="ModuleTag_01" OkToChangeModelColor="true" ExtraPublicBone="FXBONE01"> <ModelConditionState ParseCondStateType="PARSE_DEFAULT"> <Model Name="NBObelisk" /> <WeaponFireFXBone WeaponSlotType="PRIMARY_WEAPON" BoneName="FX_Weapon" /> <WeaponLaunchBone WeaponSlotType="PRIMARY_WEAPON" BoneName="FX_Weapon" /> </ModelConditionState> <ModelConditionState ParseCondStateType="PARSE_NORMAL" ConditionsYes="STRUCTURE_UNPACKING"> <Model Name="NBObeliskA_SN" /> <WeaponFireFXBone WeaponSlotType="PRIMARY_WEAPON" BoneName="FX_Weapon" /> <WeaponLaunchBone WeaponSlotType="PRIMARY_WEAPON" BoneName="FX_Weapon" /> </ModelConditionState> <ModelConditionState ParseCondStateType="PARSE_NORMAL" ConditionsYes="DAMAGED"> <Model Name="NBObeliskD2_SN" /> <WeaponFireFXBone WeaponSlotType="PRIMARY_WEAPON" BoneName="FX_Weapon" /> <WeaponLaunchBone WeaponSlotType="PRIMARY_WEAPON" BoneName="FX_Weapon" /> <ParticleSysBone BoneName="FXBone01" FXParticleSystemTemplate="NODObDamagedSteam" FollowBone="true" /> <ParticleSysBone BoneName="FXBone01" FXParticleSystemTemplate="NODObDamagedDist" FollowBone="true" /> </ModelConditionState> <ModelConditionState ParseCondStateType="PARSE_NORMAL" ConditionsYes="REALLYDAMAGED"> <Model Name="NBObeliskD2_SN" /> <WeaponFireFXBone WeaponSlotType="PRIMARY_WEAPON" BoneName="FX_Weapon" /> <WeaponLaunchBone WeaponSlotType="PRIMARY_WEAPON" BoneName="FX_Weapon" /> <ParticleSysBone BoneName="FXBone01" FXParticleSystemTemplate="NODObDamagedSpew" FollowBone="true" /> <ParticleSysBone BoneName="FXBone01" FXParticleSystemTemplate="NODObDamagedSpewDist" FollowBone="true" /> <ParticleSysBone BoneName="FXBone02" FXParticleSystemTemplate="NODObDamagedSmoke" FollowBone="true" /> </ModelConditionState> <ModelConditionState ParseCondStateType="PARSE_NORMAL" ConditionsYes="DYING"> <Model Name="NBObeliskD3_SN" /> <WeaponFireFXBone WeaponSlotType="PRIMARY_WEAPON" BoneName="FX_Weapon" /> <WeaponLaunchBone WeaponSlotType="PRIMARY_WEAPON" BoneName="FX_Weapon" /> </ModelConditionState> <AnimationState ParseCondStateType="PARSE_DEFAULT"></AnimationState> <!-- BUILD UP ANIMATION --> <AnimationState ParseCondStateType="PARSE_NORMAL" ConditionsYes="STRUCTURE_UNPACKING" StateName="STATE_Buildup"> <Animation AnimationName="NBObeliskA_AN" AnimationMode="ONCE" /> <ParticleSysBone BoneName="NONE" FXParticleSystemTemplate="Con_Small" FollowBone="true" /> </AnimationState> <AnimationState ParseCondStateType="PARSE_NORMAL" ConditionsYes="DAMAGED" StateName="STATE_Damaged" Flags="START_FRAME_FIRST"> <Animation AnimationName="NBObeliskD2_AN" AnimationMode="MANUAL" /> </AnimationState> <!-- REALLY DAMAGED ANIMATION --> <AnimationState ParseCondStateType="PARSE_NORMAL" ConditionsYes="REALLYDAMAGED" StateName="STATE_ReallyDamaged"> <Animation AnimationName="NBObeliskD2_AN" AnimationMode="ONCE" /> <Script> Prev = CurDrawablePrevAnimationState(); if Prev == "STATE_Damaged" then CurDrawableSetTransitionAnimState("TRANS_DamagedToReallyDamaged") end; </Script> </AnimationState> <!-- DYING ANIMATION --> <AnimationState ParseCondStateType="PARSE_NORMAL" ConditionsYes="DYING"> <Animation AnimationName="NBObeliskD3_AN" AnimationMode="ONCE" /> <ParticleSysBone BoneName="NONE" FXParticleSystemTemplate="Des_Small" FollowBone="false" /> </AnimationState> <!-- Transitions --> <AnimationState ParseCondStateType="PARSE_TRANSITION" ConditionsYes="TRANS_DamagedToReallyDamaged"> <Animation AnimationName="NBObeliskD2_AN" AnimationMode="ONCE" /> </AnimationState> </ScriptedModelDraw> <!-- DRAW CRYSTAL --> <ScriptedModelDraw id="ModuleTag_Draw_Crystal" OkToChangeModelColor="true"> <ModelConditionState ParseCondStateType="PARSE_DEFAULT"> <Model Name="NBObeliskCrstl" /> </ModelConditionState> <AnimationState ParseCondStateType="PARSE_DEFAULT" Flags="START_FRAME_FIRST"> <Animation AnimationName="NBObeliskCrstl" AnimationMode="MANUAL" /> </AnimationState> <AnimationState ParseCondStateType="PARSE_NORMAL" ConditionsYes="PREATTACK_A"> <Animation AnimationName="NBObeliskCrstl" AnimationMode="ONCE" /> <ParticleSysBone BoneName="FX_Weapon" FXParticleSystemTemplate="NODObeliskFireDistort" /> <ParticleSysBone BoneName="FX_Weapon" FXParticleSystemTemplate="NODObeliskFireGlow" /> </AnimationState> <AnimationState ParseCondStateType="PARSE_NORMAL" ConditionsYes="PREATTACK_C"> <Animation AnimationName="NBObeliskCrstl" AnimationMode="ONCE" /> <ParticleSysBone BoneName="FX_Weapon" FXParticleSystemTemplate="NODObeliskFireDistort" /> <ParticleSysBone BoneName="FX_Weapon" FXParticleSystemTemplate="NODObeliskFireGlow" /> </AnimationState> <AnimationState ParseCondStateType="PARSE_NORMAL" ConditionsYes="FIRING_A" Flags="START_FRAME_LAST"> <Animation AnimationName="NBObeliskCrstl" AnimationMode="MANUAL" /> </AnimationState> </ScriptedModelDraw> <SpotlightDraw id="ModuleTag_Spotlight" AttachToBoneInAnotherModule="FXBone01" RefreshTime="2.0s" SweepTime="1.0s"> <ModelConditionState ParseCondStateType="PARSE_DEFAULT"> <Model Name="FXspotlight" /> </ModelConditionState> </SpotlightDraw> <LaserDraw id="ModuleTag_LaserDraw" Texture1_UTile="1" Texture1_VTile=".5" Texture1_UScrollRate="0" Texture1_VScrollRate=".2" Texture1_NumFrames="1" Texture1_FrameRate="30" Texture2_UTile="1" Texture2_VTile=".03" Texture2_UScrollRate="0" Texture2_VScrollRate="1" Texture2_NumFrames="1" Texture2_FrameRate="30" LaserWidth="20" LaserStateID="1"> <FXShader ShaderName="Laser.fx" TechniqueIndex="0"> <Constants> <Texture Name="Texture1"> <Value>FXObeliskLaser</Value> </Texture> <Texture Name="Texture2"> <Value>FXLaserOverlay</Value> </Texture> </Constants> </FXShader> </LaserDraw> <LaserDraw id="ModuleTag_AntiInfantryLaserDraw" Texture1_UTile="1" Texture1_VTile=".5" Texture1_UScrollRate="0" Texture1_VScrollRate=".2" Texture1_NumFrames="1" Texture1_FrameRate="30" Texture2_UTile="1" Texture2_VTile=".03" Texture2_UScrollRate="0" Texture2_VScrollRate="1" Texture2_NumFrames="1" Texture2_FrameRate="30" LaserWidth="20" LaserStateID="2"> <FXShader ShaderName="Laser.fx" TechniqueIndex="0"> <Constants> <Texture Name="Texture1"> <Value>FXObeliskLaser</Value> </Texture> <Texture Name="Texture2"> <Value>FXLaserOverlay</Value> </Texture> </Constants> </FXShader> </LaserDraw> </Draws> <Behaviors> <WeaponSetUpdate id="ModuleTag_WeaponSetUpdate"> <WeaponSlotHardpoint ID="1"> <Weapon Ordering="PRIMARY_WEAPON" Template="NODObeliskLaser" /> <Weapon Ordering="SECONDARY_WEAPON" Template="NODObeliskAntiInfantryLaser" /> <Weapon Ordering="TERTIARY_WEAPON" Template="NODObeliskLaserSupport" /> </WeaponSlotHardpoint> </WeaponSetUpdate> <SpecialPower id="ModuleTag_PrismForwardingSpecialPower" SpecialPowerTemplate="SpecialPower_PrismForwarding" UpdateModuleStartsAttack="true"> </SpecialPower> <WeaponFireSpecialAbilityUpdate id="ModuleTag_PrismForwardingBeamUpdate" SpecialPowerTemplate="SpecialPower_PrismForwarding" SpecialWeapon="NODObeliskLaserSupport" StartAbilityRange="99999.0" UnpackTime="0s" WhichSpecialWeapon="1"> </WeaponFireSpecialAbilityUpdate> <LaserState id="ModuleTag_LaserState" LaserId="1" /> <SweepingLaserState id="ModuleTag_AntiInfantryLaserState" LaserId="2" Radius="25.0" SweepFXList="FX_NodLaserSweep" SweepWeapon="NODObeliskSweepWeapon" /> <SlowDeath id="ModuleTag_Death" SinkDelay="3.0s" SinkRate="4.0" DestructionDelay="8.0s"> <Sound Type="INITIAL" List="HumanFaction_SmallBuilding_DieMS" /> <DieMuxData DeathTypes="ALL" /> </SlowDeath> <StealthDetectorUpdate id="ModuleTag_StealthDetect" DetectionRange="200" /> <FXListBehavior id="ModuleTag_FXList"> <DieMuxData DeathTypes="ALL" /> <Event Index="onTransitionToDamaged" FX="FX_BuildDamaged_Medium" /> <Event Index="onTransitionToReallyDamaged" FX="FX_BuildDamaged_Medium" /> <Event Index="onTransitionToRubble" FX="FX_BuildDamaged_Medium" /> </FXListBehavior> <StructureUnpackUpdate UnpackTime="1.5s" /> <!--This is temporary and should be replaced by an inherited object--> <xi:include href="../../Includes/GenericBuildingRepair.xml" /> <xi:include href="../../Includes/GenericNODBuildingSuicide.xml" /> <xi:include href="../../Includes/RepairAlliesEngineerContain.xml" /> </Behaviors> <AI> <AIUpdate id="ModuleTag_AIUpdate" AutoAcquireEnemiesWhenIdle="YES" AILuaEventsList="NodObeliskEvents"> <UnitAITargetChooserData SympathyRange="25.0" /> </AIUpdate> </AI> <Body> <ActiveBody id="ModuleTag_ActiveBody" MaxHealth="7500.0" /> </Body> <ClientBehaviors> <ModelConditionAudioLoopClientBehavior id="ModuleTag_NOD_Obelisk_LaserChargeUp"> <ModelConditionSound Sound="NOD_Obelisk_LaserChargeUp" RequiredFlags="PREATTACK_A" ExcludedFlags="DYING" /> <ModelConditionSound Sound="NOD_Obelisk_LaserChargeUp" RequiredFlags="PREATTACK_C" ExcludedFlags="DYING" /> </ModelConditionAudioLoopClientBehavior> </ClientBehaviors> <Geometry IsSmall="false"> <Shape Type="CYLINDER" MajorRadius="18.0" Height="10.0"></Shape> <Shape Type="CYLINDER" MajorRadius="13.0" Height="10.0"> <Offset x="0" y="0" z="10" /> </Shape> <Shape Type="CYLINDER" MajorRadius="10.0" Height="10.0"> <Offset x="0" y="0" z="20" /> </Shape> <Shape Type="CYLINDER" MajorRadius="7.0" Height="15.0"> <Offset x="2" y="0" z="30" /> </Shape> <Shape Type="CYLINDER" MajorRadius="5.0" Height="15.0"> <Offset x="5" y="0" z="45" /> </Shape> <Shape Type="CYLINDER" MajorRadius="4.0" Height="14.0"> <Offset x="9" y="0" z="60" /> </Shape> <ContactPoint> <Pos x="-2.0" y="2.0" z="40" /> </ContactPoint> <ContactPoint> <Pos x="5.0" y="5.0" z="0" /> </ContactPoint> <ContactPoint> <Pos x="2.0" y="2.0" z="40" /> </ContactPoint> <ContactPoint> <Pos x="-5.0" y="-5.0" z="0" /> </ContactPoint> <ContactPoint> <Pos x="2.0" y="-2.0" z="40" /> </ContactPoint> <ContactPoint> <Pos x="5.0" y="-5.0" z="0" /> </ContactPoint> <ContactPoint> <Pos x="-2.0" y="-2.0" z="40" /> </ContactPoint> <ContactPoint> <Pos x="-5.0" y="5.0" z="0" /> </ContactPoint> </Geometry> <AudioArrayVoice> <AudioEntry Sound="NOD_ObeliskOfLightSelect" AudioType="voiceSelect" /> </AudioArrayVoice> <AudioArraySound> <AudioEntry Sound="HumanFaction_SmallBuilding_LightDamageMS" AudioType="soundOnDamaged" /> <AudioEntry Sound="HumanFaction_SmallBuilding_HeavyDamageMS" AudioType="soundOnReallyDamaged" /> </AudioArraySound> <ShadowInfo Type="VOLUME" /> <VisionInfo VisionRange="450" ShroudClearingRange="500" /> <ProjectedBuildabilityInfo Radius="10.0" BuildPlacementTypes="MAIN_STRUCTURE OTHER_STRUCTURE" /> </GameObject> <WeaponTemplate id="NODObeliskLaserSupport" Name="NODObeliskLaserSupport" AttackRange="375.0" CanFireWhileMoving="false" WeaponSpeed="999999.0" FireSound="NOD_Obelisk_LaserFireMS" RadiusDamageAffects="ALLIES" AcceptableAimDelta="180d" PreAttackType="PER_SHOT" ReAcquireDetailType="PRE_FIRE" ClipSize="1" AutoReloadsClip="AUTO"> <PreAttackDelay MinSeconds="2s" MaxSeconds="2s" /> <FiringDuration MinSeconds="1s" MaxSeconds="1s" /> <ClipReloadTime MinSeconds="1.0s" MaxSeconds="1.0s" /> <Nuggets> <ActivateLaserNugget Lifetime="0.75s" LaserId="1"> <SpecialObjectFilter Rule="NONE" Relationship="SAME_PLAYER"> <IncludeThing>NODObelisk</IncludeThing> </SpecialObjectFilter> </ActivateLaserNugget> <DamageNugget Damage="0.0" Radius="0.0" DelayTimeSeconds="0.1s" DamageType="CANNON" DamageFXType="NOD_LASER" DeathType="NORMAL"> <SpecialObjectFilter Rule="NONE" Relationship="SAME_PLAYER"> <IncludeThing>NODObelisk</IncludeThing> </SpecialObjectFilter> </DamageNugget> </Nuggets> </WeaponTemplate> <WeaponTemplate id="PrismTowerSupportWeapon" Name="PrismTowerSupportWeapon" RadiusDamageAffects="ALLIES" AttackRange="15000.0"> <Nuggets> <AttributeModifierNugget Radius="15000" PartitionFilterTestType="SPHERE" AttributeModifierName="AttributeModifier_PrismTowerSupport"> <SpecialObjectFilter Rule="ANY" Relationship="SAME_PLAYER"> <IncludeThing>NODObelisk</IncludeThing> </SpecialObjectFilter> </AttributeModifierNugget> </Nuggets> </WeaponTemplate> <AttributeModifier id="AttributeModifier_PrismTowerSupport" Category="BUFF" StackingLimit="999" Duration="4.0s"> <Modifier Type="DAMAGE_MULT" Value="100%"/> </AttributeModifier> <!-- actually not needed if solution via tertiary weapon --> <SpecialPowerTemplate id="SpecialPower_PrismForwarding" Flags="NEEDS_OBJECT_FILTER" TargetType="OBJECT"> <ObjectFilter Relationship="SAME_PLAYER" Rule="NONE"> <IncludeThing>NODObelisk</IncludeThing> </ObjectFilter> </SpecialPowerTemplate> <UnitAbilityButtonTemplate id="ButtonSpecialPower_PrismForwarding" LogicCommand="Command_SpecialPower_PrismForwarding"> <Data> <TargetedSpecialPower StateData="ButtonStateSpecialPower_PrismForwarding" ValidTargetCursor="Bombard" /> </Data> </UnitAbilityButtonTemplate> <ButtonSingleStateData id="ButtonStateSpecialPower_PrismForwarding"> <State Image="Portrait_NODObeliskofLight" Title="Name:NODObelisk"/> </ButtonSingleStateData> <LogicCommand Options="NEED_TARGET_ALLY_OBJECT" Type="SPECIAL_POWER" id="Command_SpecialPower_PrismForwarding"> <SpecialPower>SpecialPower_PrismForwarding</SpecialPower> </LogicCommand> <LogicCommandSet id="NODObeliskCommandSet"> <Cmd>Command_SpecialPower_PrismForwarding</Cmd> </LogicCommandSet> </AssetDeclaration> Edited May 27, 2017 by Mjjstral 1 Share this post Link to post
Madin 10 Posted May 27, 2017 Thanks for your detailed input! I am back to errors again, I am using the 'NAME_ATTACK_NAMED' method. Below is the error: Quote ErrorLogToFile(05/27/17 19:06:25): error: attempt to index global `GetObj' (a nil value) stack traceback: 1: function `CascadeForwarding' at line 48 [string "Data\Scripts\Scripts.lua"] Line 48 is: self=GetObj.Table(self) Here is the full Lua code: function GetMyMainTower(object) for q,p in MainTowerSupporterList do if q~=tostring(object) then for i=1,getn(p),1 do if p[i]==object then return p[1] end end end end end function IsTowerStillAttacking(MainTower) if ObjectTestModelCondition(MainTower,"ATTACKING") or ObjectTestModelCondition(MainTower,"PREATTACK_A") or ObjectTestModelCondition(MainTower,"RELOADING_A") or ObjectTestModelCondition(MainTower,"FIRING_A") then return true else return false end end function NodObelisk_RELOADING(self) local TowerAttackRange = 375 if MainTowerSupporterList[tostring(self)]~=nil then --and ObjectCountNearbyEnemies(self,TowerAttackRange)==0 then for i=2,getn(MainTowerSupporterList[tostring(self)]),1 do ExecuteAction("NAMED_STOP",MainTowerSupporterList[tostring(self)][i]) end --MainTowerSupporterList[tostring(self)]=nil else if ObjectCountNearbyEnemies(self,TowerAttackRange)==0 then ExecuteAction("NAMED_STOP",self) end local MainTower = GetMyMainTower(self) if not IsTowerStillAttacking(MainTower) then MainTowerSupporterList[tostring(MainTower)]=nil else ExecuteAction("NAMED_STOP",self) ExecuteAction("NAMED_ATTACK_NAMED",self,MainTowerSupportConnectionList[tostring(MainTower)][tostring(self)]) end end end function getn2(table) local size = 0 for k,v in table do size=size+1 end return size end MainTowerSupporterList= {} MainTowerSupportConnectionList= {} function CascadeForwarding(self) --made for "Madin" (cncnz forums) self=GetObj.Table(self) local IsObjectInSupporterList = function(object) for q,p in MainTowerSupporterList do --to test if a tower already supports another tower if q~=tostring(%self) then for i=1,getn(p),1 do if p[i]==object then return true end end end end return false end local MaximumSupportDistance = 13000 local TowerAttackRange = 375 --here attack range of nod obelisk if ObjectCountNearbyEnemies(self,TowerAttackRange)==0 and IsObjectInSupporterList(self) then return end local ObjectList = {} local DistanceTable = {} local SupportConnection={} --create object list of all support towers for k,v in globals() do if strfind(k,"ObjID") and GetObj.Hash(self)==GetObj.Hash(v) and self~=v and ObjectTeamName(self)==ObjectTeamName(v) then if ObjectCountNearbyEnemies(v,TowerAttackRange)==0 and EvaluateCondition("DISTANCE_BETWEEN_OBJ", GetObj.String(self),GetObj.String(v), CompareTable["<="], MaximumSupportDistance) then if not IsObjectInSupporterList(v) then tinsert(ObjectList,v) end end end end tinsert(ObjectList,self) if getn(ObjectList)<2 then return end --create 2d matrix with distances for i=1,getn(ObjectList),1 do DistanceTable[tostring(ObjectList[i])]={} end for i=1,getn(ObjectList),1 do for j=i,getn(ObjectList),1 do if i==j then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=0 else if DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] == nil then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=GetObjectDistance(GetObj.String(ObjectList[i]),GetObj.String(ObjectList[j])) DistanceTable[tostring(ObjectList[j])][tostring(ObjectList[i])]=DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] end end end end --sort object list by distance to main tower local SortHelper=function(Object1,Object2) if %DistanceTable[tostring(%self)][tostring(Object1)] < %DistanceTable[tostring(%self)][tostring(Object2)] then return true end end sort(ObjectList,SortHelper) --main part: distribute one tower for each tower to support local MainNodeTable = {} tinsert(MainNodeTable,self) local i = 2 local ObjectListSize for i=2,getn(ObjectList),1 do local MainNodeCriteria = true local NextMainNode, NextMainNodeAlternative --closest main node local NextMainNodeDistance = 13000 for k=1,getn(MainNodeTable),1 do if NextMainNodeDistance > DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] then NextMainNodeDistance = DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] NextMainNode = MainNodeTable[k] end end local ShortestLinkToNextMainNodeDistance = 13000 for j=3,getn(ObjectList),1 do if not i==j and ObjectList[j]~=nil then local iNodeTojNodeDistance = DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] local iNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[i])] local jNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[j])] if jNodeToNextMainNodeDistance < iNodeToNextMainNodeDistance then if iNodeTojNodeDistance < iNodeToNextMainNodeDistance then MainNodeCriteria = false local LinkToNextMainNodeDistance = iNodeTojNodeDistance + jNodeToNextMainNodeDistance if ShortestLinkToNextMainNodeDistance > LinkToNextMainNodeDistance then NextMainNodeAlternative=ObjectList[j] ShortestLinkToNextMainNodeDistance = LinkToNextMainNodeDistance end end end end end if MainNodeCriteria == true then if TowerAttackRange > DistanceTable[tostring(NextMainNode)][tostring(ObjectList[i])] then tinsert(MainNodeTable,ObjectList[i]) SupportConnection[tostring(ObjectList[i])]=NextMainNode else ObjectList[i]=nil end else if TowerAttackRange > ShortestLinkToNextMainNodeDistance then SupportConnection[tostring(ObjectList[i])]=NextMainNodeAlternative else ObjectList[i]=nil end end end --remove all unneccasary towers local CleanedObjectList = {} for i=1,getn(ObjectList),1 do if not ObjectList[i]==nil then tinsert(CleanedObjectList,ObjectList[i]) end end ObjectList=CleanedObjectList MainTowerSupporterList[tostring(self)] = CleanedObjectList MainTowerSupportConnectionList[tostring(self)]=SupportConnection --fire cumulative attribute modifier weapon on main tower (self) for i=2,getn(ObjectList),1 do ObjectCreateAndFireTempWeapon(self,"PrismTowerSupportWeapon") end --fire special power to display visual support beams for i=2,getn(ObjectList),1 do --ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_NAMED",ObjectList[i],"SpecialPower_PrismForwarding",SupportConnection[tostring(ObjectList[i])]) ExecuteAction("NAMED_STOP",ObjectList[i]) ExecuteAction("NAMED_ATTACK_NAMED",ObjectList[i],SupportConnection[tostring(ObjectList[i])]) end end function _ALERT(message) ExecuteAction("SHOW_MILITARY_CAPTION", "\n\n\n\n" .. tostring(message) .. "\n", 20) local file = "" local filehandle = openfile("ErrorLog",'a') if filehandle ~= nil then write(filehandle,"\n" .. "ErrorLogToFile(" .. date() .. "): " .. message) else local filehandle = writeto(file) write(filehandle,"\n" .. "ErrorLogToFile(" .. date() .. "): " .. message) end flush(filehandle) closefile(filehandle) end That error log and message is really helpful! Share this post Link to post
Mjjstral 25 Posted May 27, 2017 Ok you also need to include all functions from previous posts. And be aware to always take the most recent functions I've posted. It can't find the GetObj table in which we stored the functions like GetObj.String and more. See above, we declared GetObj = {}. 1 Share this post Link to post
Madin 10 Posted May 27, 2017 (edited) 30 minutes ago, Mjjstral said: Ok you also need to include all functions from previous posts. And be aware to always take the most recent functions I've posted. It can't find the GetObj table in which we stored the functions like GetObj.String and more. See above, we declared GetObj = {}. OK thanks! 3 hours ago, Mjjstral said: The very strange thing for me was, that every second support beam, does damage to the tower they support. This can probably get solved by reissuing the attack command precisely after each attack. Same happening for me! for i=2,getn(ObjectList),1 do --ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_NAMED",ObjectList[i],"SpecialPower_PrismForwarding",SupportConnection[tostring(ObjectList[i])]) ExecuteAction("NAMED_STOP",ObjectList[i]) ExecuteAction("NAMED_ATTACK_NAMED",ObjectList[i],SupportConnection[tostring(ObjectList[i])]) ExecuteAction("NAMED_ATTACK_NAMED",ObjectList[i],SupportConnection[tostring(ObjectList[i])]) end ? If the main 'PRIMARY' weapon cannot damage Obelisk from the same team, will it cause code issues? Edited May 27, 2017 by Madin Question Share this post Link to post
Mjjstral 25 Posted May 27, 2017 Quote If the main 'PRIMARY' weapon cannot damage Obelisk from the same team, will it cause code issues? That was my idea too, just try it. Should't interfere with the script. Or use the special power 1 Share this post Link to post
Madin 10 Posted May 27, 2017 55 minutes ago, Madin said: If the main 'PRIMARY' weapon cannot damage Obelisk from the same team, will it cause code issues? Apparently the answer is that it will cause code issues! The Obelisk get stuck in a support loop eventually. They start off targeting enemy units. But once a the enemy units stop coming, the Obelisk start firing on each other. I am going to have another attempt at the special power, I do not even understand why it is not working. Share this post Link to post
Madin 10 Posted May 27, 2017 (edited) For whatever reason, I cannot get a 'Weaponfireabilityupdate' to work. I manually tested a modified 'Beamabilityupdate' and it worked: <SpecialPower id="ModuleTag_PrismForwardingSpecialPower" SpecialPowerTemplate="SpecialPower_PrismForwarding" UpdateModuleStartsAttack="true"></SpecialPower> <BeamSpecialAbilityUpdate id="ModuleTag_ChargeDefensesSPUpdate" SpecialPowerTemplate="SpecialPower_PrismForwarding" JoinWithOtherBeams="false" UnpackTime="2s" UnpackSound="NOD_Obelisk_LaserChargeUp" PrepSoundLoop="NOD_Obelisk_LaserFireMS" PackTime="1s" PreparationTime="0.5s" StartAbilityRange="375.0" TargetSamePlayerOnly="true" Options="CHECK_SPECIALPOWER_REQUIREMENTS_DURING_UPDATE" PreferredTargetBone="FXBONE01" DamagePerSecond="0.0"> <!--TargetAttributeModifierAdd="AttributeModifier_ChargeDefenses"--> </BeamSpecialAbilityUpdate> However, when I attempt to use it with the LUA script, nothing happens! (no errors, just no support beam). Script: GetObj={} function GetObj.String(object) if type(object) == "string" then return object else local _, count = gsub(ObjectDescription(object),"%(","") if count>1 then return strsub(ObjectDescription(object), strfind(ObjectDescription(object),"(",1,true)+1,strfind(ObjectDescription(object),")",1,true)-1) else local StrRef = "object" .. RandomString(5) ExecuteAction("SET_UNIT_REFERENCE", StrRef, object) return StrRef end end end function GetObj.Table(object) if type(object) == "table" then return object else for k,v in globals() do if strfind(k,"ObjID") ~= nil and strfind(ObjectDescription(v),object) then return rawget(globals(),k) end end return object end end function GetObj.Hash(object) return tostring(tonumber(strsub(ObjectTemplateName(GetObj.Table(object)), 6, 13), 16)) end CompareTable = { LT=0, LE=1, EQ=2, GE=3, GT=4, NE=5, ["<"]=0, ["<="]=1, ["=="]=2, [">="]=3, [">"]=4, ["~="]=5 } function GetObjectDistance(object1 ,object2, start_distance) if object1==nil or object2==nil or not EvaluateCondition("NAMED_NOT_DESTROYED",object1) or not EvaluateCondition("NAMED_NOT_DESTROYED",object2) then return 0 end object1=GetObj.String(object1) object2=GetObj.String(object2) local v = {} v.GetDistance = function(lowerlimit,upperlimit) local mid=(upperlimit+lowerlimit)/2 if upperlimit-lowerlimit<=1 then return lowerlimit elseif EvaluateCondition("DISTANCE_BETWEEN_OBJ", %object1, %object2, CompareTable["<"], mid) then return %v.GetDistance(lowerlimit,mid) else return round(%v.GetDistance(mid,upperlimit)) end end if start_distance then return v.GetDistance(0,tonumber(start_distance)) else return v.GetDistance(0,12870) end --maxdiagsize=ceil(sqrt(2*(7500+2*800)^2))) end function random(...) --overwritting lua native function for multiplayer compatibility local RandomNumber = function(a,b) return floor(a+((b-a)*GetRandomNumber())+0.5) end if getn(arg) == 0 then return floor(GetRandomNumber()+0.5) elseif getn(arg) == 1 then return RandomNumber(1,arg[1]) elseif getn(arg) == 2 then return RandomNumber(arg[1],arg[2]) else return arg[RandomNumber(1,getn(arg))] end end function RandomString(l) if l < 1 then l=5 end local s = "" local ascii_zone = {} for i=1,l,1 do ascii_zone[1] = random(97, 122) ascii_zone[2] = random(65, 90) ascii_zone[3] = random(48, 57) s = s .. strchar(ascii_zone[random(3)]) end return ("_" .. s) end function GetMyMainTower(object) for q,p in MainTowerSupporterList do if q~=tostring(object) then for i=1,getn(p),1 do if p[i]==object then return p[1] end end end end end function IsTowerStillAttacking(MainTower) if ObjectTestModelCondition(MainTower,"ATTACKING") or ObjectTestModelCondition(MainTower,"PREATTACK_A") or ObjectTestModelCondition(MainTower,"RELOADING_A") or ObjectTestModelCondition(MainTower,"FIRING_A") then return true else return false end end function NodObelisk_RELOADING(self) local TowerAttackRange = 375 if MainTowerSupporterList[tostring(self)]~=nil then --and ObjectCountNearbyEnemies(self,TowerAttackRange)==0 then for i=2,getn(MainTowerSupporterList[tostring(self)]),1 do ExecuteAction("NAMED_STOP",MainTowerSupporterList[tostring(self)][i]) end --MainTowerSupporterList[tostring(self)]=nil else if ObjectCountNearbyEnemies(self,TowerAttackRange)==0 then ExecuteAction("NAMED_STOP",self) end local MainTower = GetMyMainTower(self) if not IsTowerStillAttacking(MainTower) then MainTowerSupporterList[tostring(MainTower)]=nil else ExecuteAction("NAMED_STOP",self) ExecuteAction("NAMED_ATTACK_NAMED",self,MainTowerSupportConnectionList[tostring(MainTower)][tostring(self)]) end end end function getn2(table) local size = 0 for k,v in table do size=size+1 end return size end function round(number) return floor(number+0.5) end MainTowerSupporterList= {} MainTowerSupportConnectionList= {} function CascadeForwarding(self) --made for "Madin" (cncnz forums) self=GetObj.Table(self) local IsObjectInSupporterList = function(object) for q,p in MainTowerSupporterList do --to test if a tower already supports another tower if q~=tostring(%self) then for i=1,getn(p),1 do if p[i]==object then return true end end end end return false end local MaximumSupportDistance = 13000 local TowerAttackRange = 375 --here attack range of nod obelisk if ObjectCountNearbyEnemies(self,TowerAttackRange)==0 and IsObjectInSupporterList(self) then return end local ObjectList = {} local DistanceTable = {} local SupportConnection={} --create object list of all support towers for k,v in globals() do if strfind(k,"ObjID") and GetObj.Hash(self)==GetObj.Hash(v) and self~=v and ObjectTeamName(self)==ObjectTeamName(v) then if ObjectCountNearbyEnemies(v,TowerAttackRange)==0 and EvaluateCondition("DISTANCE_BETWEEN_OBJ", GetObj.String(self),GetObj.String(v), CompareTable["<="], MaximumSupportDistance) then if not IsObjectInSupporterList(v) then tinsert(ObjectList,v) end end end end tinsert(ObjectList,self) if getn(ObjectList)<2 then return end --create 2d matrix with distances for i=1,getn(ObjectList),1 do DistanceTable[tostring(ObjectList[i])]={} end for i=1,getn(ObjectList),1 do for j=i,getn(ObjectList),1 do if i==j then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=0 else if DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] == nil then DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])]=GetObjectDistance(GetObj.String(ObjectList[i]),GetObj.String(ObjectList[j])) DistanceTable[tostring(ObjectList[j])][tostring(ObjectList[i])]=DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] end end end end --sort object list by distance to main tower local SortHelper=function(Object1,Object2) if %DistanceTable[tostring(%self)][tostring(Object1)] < %DistanceTable[tostring(%self)][tostring(Object2)] then return true end end sort(ObjectList,SortHelper) --main part: distribute one tower for each tower to support local MainNodeTable = {} tinsert(MainNodeTable,self) local i = 2 local ObjectListSize for i=2,getn(ObjectList),1 do local MainNodeCriteria = true local NextMainNode, NextMainNodeAlternative --closest main node local NextMainNodeDistance = 13000 for k=1,getn(MainNodeTable),1 do if NextMainNodeDistance > DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] then NextMainNodeDistance = DistanceTable[tostring(MainNodeTable[k])][tostring(ObjectList[i])] NextMainNode = MainNodeTable[k] end end local ShortestLinkToNextMainNodeDistance = 13000 for j=3,getn(ObjectList),1 do if not i==j and ObjectList[j]~=nil then local iNodeTojNodeDistance = DistanceTable[tostring(ObjectList[i])][tostring(ObjectList[j])] local iNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[i])] local jNodeToNextMainNodeDistance = DistanceTable[tostring(NextMainNode)][tostring(ObjectList[j])] if jNodeToNextMainNodeDistance < iNodeToNextMainNodeDistance then if iNodeTojNodeDistance < iNodeToNextMainNodeDistance then MainNodeCriteria = false local LinkToNextMainNodeDistance = iNodeTojNodeDistance + jNodeToNextMainNodeDistance if ShortestLinkToNextMainNodeDistance > LinkToNextMainNodeDistance then NextMainNodeAlternative=ObjectList[j] ShortestLinkToNextMainNodeDistance = LinkToNextMainNodeDistance end end end end end if MainNodeCriteria == true then if TowerAttackRange > DistanceTable[tostring(NextMainNode)][tostring(ObjectList[i])] then tinsert(MainNodeTable,ObjectList[i]) SupportConnection[tostring(ObjectList[i])]=NextMainNode else ObjectList[i]=nil end else if TowerAttackRange > ShortestLinkToNextMainNodeDistance then SupportConnection[tostring(ObjectList[i])]=NextMainNodeAlternative else ObjectList[i]=nil end end end --remove all unneccasary towers local CleanedObjectList = {} for i=1,getn(ObjectList),1 do if not ObjectList[i]==nil then tinsert(CleanedObjectList,ObjectList[i]) end end ObjectList=CleanedObjectList MainTowerSupporterList[tostring(self)] = CleanedObjectList MainTowerSupportConnectionList[tostring(self)]=SupportConnection --fire cumulative attribute modifier weapon on main tower (self) for i=2,getn(ObjectList),1 do ObjectCreateAndFireTempWeapon(self,"PrismTowerSupportWeapon") end --fire special power to display visual support beams for i=2,getn(ObjectList),1 do ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_NAMED",ObjectList[i],"SpecialPower_PrismForwarding",SupportConnection[tostring(ObjectList[i])]) -- ExecuteAction("NAMED_STOP",ObjectList[i]) -- ExecuteAction("NAMED_ATTACK_NAMED",ObjectList[i],SupportConnection[tostring(ObjectList[i])]) end end function _ALERT(message) ExecuteAction("SHOW_MILITARY_CAPTION", "\n\n\n\n" .. tostring(message) .. "\n", 20) local file = "" local filehandle = openfile("ErrorLog",'a') if filehandle ~= nil then write(filehandle,"\n" .. "ErrorLogToFile(" .. date() .. "): " .. message) else local filehandle = writeto(file) write(filehandle,"\n" .. "ErrorLogToFile(" .. date() .. "): " .. message) end flush(filehandle) closefile(filehandle) end Am I doing something wrong? (maybe it has to be a 'WeaponFireSpecialAbilityUpdate' to work?) or does it have to be done using 'NAME_ATTACK_NAMED'? Edited May 27, 2017 by Madin Share this post Link to post
Mjjstral 25 Posted May 27, 2017 (edited) In general your options for special powers/commandbuttons/abilities/attacking are: ExecuteAction("NAMED_FIRE_SPECIAL_POWER",...) ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_NAMED",...) ExecuteAction("NAMED_FIRE_SPECIAL_POWER_AT_WAYPOINT",...) ExecuteAction("NAMED_FIRE_WEAPON_FOLLOWING_WAYPOINT_PATH",...) ExecuteAction("NAMED_FIRE_METAGAME_OP_INSTANT",...) ExecuteAction("NAMED_USE_COMMANDBUTTON_ABILITY",...) ExecuteAction("NAMED_USE_COMMANDBUTTON_ABILITY_AT_WAYPOINT",...) ExecuteAction("NAMED_USE_COMMANDBUTTON_ABILITY_ON_NAMED",...) ExecuteAction("NAMED_USE_COMMANDBUTTON_ON_NEAREST_ENEMY_BUILDING",...) ExecuteAction("NAMED_USE_COMMANDBUTTON_ON_NEAREST_ENEMY_BUILDING_CLASS",...) ExecuteAction("NAMED_USE_COMMANDBUTTON_ON_NEAREST_ENEMY_UNIT",...) ExecuteAction("NAMED_USE_COMMANDBUTTON_ON_NEAREST_GARRISONED_BUILDING",...) ExecuteAction("NAMED_USE_COMMANDBUTTON_ON_NEAREST_KINDOF",...) ExecuteAction("NAMED_USE_COMMANDBUTTON_ON_NEAREST_OBJECTTYPE",...) ExecuteAction("NAMED_ATTACK_NAMED",...) ObjectDoSpecialPower(object,specialpowername) Try to figure out a way to use your sp with these commands, else WeaponFireSpecialAbilityUpdate could be mandatory/easier to use ? ExecuteAction("NAMED_USE_COMMANDBUTTON_ABILITY_ON_NAMED",...) looks promising. Quote or does it have to be done using 'NAME_ATTACK_NAMED'? No the code will work with a special power too, but uncomment "--MainTowerSupporterList[tostring(self)]=nil" in NodObelisk_RELOADING function then. Edited May 27, 2017 by Mjjstral Share this post Link to post
Mjjstral 25 Posted May 27, 2017 Just a sidenote observation: You can trigger an untargeted special power in 4 ways, it's crazy: ExecuteAction("NAMED_FIRE_SPECIAL_POWER",...) ExecuteAction("NAMED_FIRE_METAGAME_OP_INSTANT",...) ExecuteAction("NAMED_USE_COMMANDBUTTON_ABILITY",...) ObjectDoSpecialPower(object,specialpowername) And a lame single forwarded prism forwarding could be achieved with ExecuteAction("NAMED_USE_COMMANDBUTTON_ON_NEAREST_OBJECTTYPE",...) Plus some testings of course. Share this post Link to post
Madin 10 Posted May 27, 2017 For whatever reason, the Special power method is doing nothing. I tried 'NAMED_USE_COMMANDBUTTON_ABILITY_ON_NAMED', and also tried a test with a ' WeaponFireSpecialAbilityUpdate', using both 'NAMED_FIRE_SPECIAL_POWER_AT_NAMED' and 'NAMED_USE_COMMANDBUTTON_ABILITY_ON_NAMED' methods. I used a power that I was sure worked (Orca targeted sensor pod), tested the button to make sure it worked, and after all that the Lua script did nothing with it. I am going back to the attack method. Share this post Link to post