• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Another Hero AI

Status
Not open for further replies.

Deleted member 219079

D

Deleted member 219079

Current process:
JASS:
scope PaladinAI

	globals
		private player filtPlayer
		private group G = CreateGroup()
		private unit U
	endglobals
	
	private function AliveEnemyFilter takes nothing returns boolean
		return AliveAttackerFilter() and AttackableFilter() and IsUnitEnemy(GetFilterUnit(),filtPlayer)
	endfunction
	
	private function FriendFilter takes nothing returns boolean
		return AliveAttackerFilter() and AttackableFilter() and IsUnitAlly(GetFilterUnit(),filtPlayer)
	endfunction
	
	private function JuicyEnemyFilter takes nothing returns boolean
		set U = GetFilterUnit()
		return AliveEnemyFilter() and /*
			*/ 0.33>GetUnitState(U,UNIT_STATE_LIFE)/GetUnitState(U,UNIT_STATE_MAX_LIFE)
	endfunction
	
	private function BackOff takes unit u, boolean aggro returns boolean
		if aggro then
			return IssuePointOrder(u,"attack",(GetPlayerId(GetOwningPlayer(u))*2-1)*3000,0)
		endif
		return IssuePointOrder(u,"move",(GetPlayerId(GetOwningPlayer(u))*2-1)*3000,0)
	endfunction

	public function LowStats takes nothing returns nothing
		call BackOff(HeroAI(GetTimerData(GetExpiredTimer())).me,false)
	endfunction
	
	//! textmacro PALADIN_ATK_LOCALS takes PALA
		local unit FoG
		local real x = GetUnitX($PALA$)
		local real y = GetUnitY($PALA$)
		local real hp
		local real r
	//! endtextmacro
	//! textmacro PALADIN_ATK takes PALA, RANGE, VAR
		set r = 9999
		set filtPlayer = GetOwningPlayer($PALA$)
		call GroupEnumUnitsInRange(G,x,y,$RANGE$,Filter(function AliveEnemyFilter))
		loop
			set FoG = FirstOfGroup(G)
			exitwhen FoG == null
			set hp = GetUnitState(FoG,UNIT_STATE_LIFE)
			if hp<r then
				set r=hp
				set $VAR$=FoG
			endif
			call GroupRemoveUnit(G,FoG)
		endloop
	//! endtextmacro
	
	public function Attack takes nothing returns nothing
		local HeroAI ai = GetTimerData(GetExpiredTimer())
		local unit u = ai.me
		local unit target
		//! runtextmacro PALADIN_ATK_LOCALS("u")
		//! runtextmacro PALADIN_ATK("u","300","target")
		if target==null then
			set target = GetClosestUnitInRange(x,y,ai.targetConsiderRange,Filter(function AliveEnemyFilter))
			if target != null then
				call IssueTargetOrder(u,"attack",target)
				set target = null
			endif
		else
			call IssueTargetOrder(u,"attack",target)
			set target = null
		endif
		set u = null
	endfunction
	
	public function FallBack takes nothing returns nothing
		local unit u = HeroAI(GetTimerData(GetExpiredTimer())).me
		local unit target
		local integer c
		//! runtextmacro PALADIN_ATK_LOCALS("u")
		if GetUnitAttackers(u) > 0 then
			set target = GetClosestUnitInRange(GetUnitX(u),GetUnitY(u),300,Filter(function JuicyEnemyFilter))
		else
			set filtPlayer=GetOwningPlayer(u)
			call GroupEnumUnitsInRange(G,GetUnitX(u),GetUnitY(u),500,Filter(function FriendFilter))
			set c = 0
			loop
				set FoG = FirstOfGroup(G)
				exitwhen FoG==null
				set c=c+1
				call GroupRemoveUnit(G,FoG)
			endloop
			if c>2 then
				//! runtextmacro PALADIN_ATK("u","450","target")
			else
				set target=null
			endif
		endif
		if target == null then
			call BackOff(u,false)
		elseif GetUnitTarget(u)!=target then
			call AddIndicator(u,255,255,255,255)
			call IssueTargetOrder(u,"attack",target)
		endif
	endfunction
	
	public function Assault takes nothing returns nothing
		local unit u = HeroAI(GetTimerData(GetExpiredTimer())).me
		if GetUnitState(u,UNIT_STATE_LIFE)/GetUnitState(u,UNIT_STATE_MAX_LIFE)>0.8 then
			call IssuePointOrder(u,"attack",(GetPlayerId(GetOwningPlayer(u))*2-1)*-3000,0)
		else
			call BackOff(u,true)
		endif
		set u = null
	endfunction
	
// ---------------

	//! textmacro PALADIN_INI takes U, AI
			// Basic :
		set $AI$ = HeroAI.create($U$)
		set $AI$.onLowLife = function PaladinAI_LowStats
		set $AI$.onFallback = function PaladinAI_FallBack
		set $AI$.onAttack = function PaladinAI_Attack
		set $AI$.onOffend = function PaladinAI_Assault
			// Uses SkillMod - plugin :
		static if HeroAI.HAS_SKILLMOD then
			call $AI$.enableSkillMod()
			set $AI$[1] = A_HOLYLIGHT
			set $AI$[2] = A_DIVINESHIELD
			set $AI$[3] = A_HOLYLIGHT
			set $AI$[4] = A_DEVOTIONAURA
			set $AI$[5] = A_HOLYLIGHT
			set $AI$[6] = A_RESURRECTION
			set $AI$[7] = A_DIVINESHIELD
			set $AI$[8] = A_DEVOTIONAURA
			set $AI$[9] = A_DEVOTIONAURA
			set $AI$[10] = A_DIVINESHIELD
			call $AI$.checkLevelups()
		endif
			// Speed stuff up:
		//call UnitAddItem($U$,CreateItem('ratf',0,0))
	//! endtextmacro
	
endscope

Currently the demo map is following:
attachment.php

Where on the red circle stand 2 paladings after the scope init.

What this does at the moment?
It orders the paladin to move to point -3168 x and -3040 y when it hits low health.

In this case 2 Paladins, as I'm also testing the MUI capabilities, and running multiple units at the same time seems to work just fine.

There's still a lot to do but I'll keep ye updated :)
 

Attachments

  • asdf.PNG
    asdf.PNG
    408.6 KB · Views: 441
Last edited by a moderator:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Threat is a very subjective parameter (it totally depends on the map).
A very common way is to check which unit does the highest avarage/total damage.
Everything else i.e. retreating, outnumbered, lowstats, archer, slowed, ... is different from map to map.

You could add a IsUnitStunned function, if not already existing, to your stun system, where you check for the ability level of the stun buff.
 

Deleted member 219079

D

Deleted member 219079

My own one. That's what I'm showcasing here.
 
I don't quite get the point of this thread. Neither to you provide actual code nor do you provide anything to discuss about it. You just showcase a simple API based on another custom resource of you that we have no access to.

Also, don't write your own event lib. The existing event lib leaves nothing to be desired for.

For your questions, you might want to take a look into this:
http://www.hiveworkshop.com/forums/...tem-2-6-a-156179/?prev=status=a&u=Zwiebelchen

It's a threat system counting individual threat of units towards other units based on a point-system, applying attack orders to the unit that has the highest threat.
 

Deleted member 219079

D

Deleted member 219079

Edit: As I made a big update to OffenseStrength library, I won't need help on that one (I changed the structure to require StructuredDD).

So here's the first library I'll showcase to you, it's free to use and pretty efficient. You don't need to modify it in any way to work. Use it how you wish to as long as you credit me and the creators of PUI and StructuredDD (cohadar and cokemonkey).
JASS:
library OffenseStrength initializer onInit requires PUI, StructuredDD
	globals
		private constant integer MAX_SCANS = 10 // on how many attacks is the avg dmg got from?
		constant real UNKNOWN_STRENGTH = -1
	endglobals
	private struct DmgCounter extends array
		readonly integer count
		readonly real dmg
		readonly static unit array uArr
		static method create takes unit u, real dmg returns thistype
			local thistype this = GetUnitIndex(u)
			if not (count > 0) then // uninitialized instance
				set count = 1
				set .dmg = dmg
				set uArr[this] = u
			elseif uArr[this] == u then // properly set instance
				if count < MAX_SCANS then
					set count = count + 1
					set .dmg = .dmg + dmg
				endif
			else // outdated instance (old unit doesn't match new unit)
				set count = 1
				set .dmg = dmg
				set uArr[this] = u
			endif
			return this
		endmethod
	endstruct
	function GetUnitDamage takes unit u returns real
		local DmgCounter dc = GetUnitIndex(u)
		if DmgCounter.uArr[dc] == u then
			return dc.dmg / dc.count
		endif
		return UNKNOWN_STRENGTH
	endfunction
	private function onHit takes nothing returns boolean
		call DmgCounter.create(GetEventDamageSource(),GetEventDamage())
		return false
	endfunction
	private function onInit takes nothing returns nothing
		call StructuredDD.addHandler(function onHit)
	endfunction
endlibrary

Edit: Updated the demo scope on OP!
 
Last edited by a moderator:

Deleted member 219079

D

Deleted member 219079

Can anyone tell me if there's a good system for getting unit's target? I'd do good use for functions such as:
unit GetUnitTarget(unit)
integer GetUnitAttackers(unit)
void ForgetUnitTarget(unit)
Basically I'd need a system that counts how many units are attacking it at the moment.

And I won't even look at resources that use something else as their UI than the Perfect Unit Indexer.

Edit: NEED HELP!
So I didn't find any system to this, I tried to create it myself:
JASS:
library UnitTarget requires PUI, StructuredDD
	struct Attackers extends array
		private static unit array uArr	// instance host
		private integer i
		static method operator [] takes unit u returns integer
			local thistype this = GetUnitIndex(u)
			if u != uArr[this] then
				set uArr[this] = u
				set i = 0
			endif
			return i
		endmethod
		method shiftValue takes integer by returns nothing
			if i+by > 0 then
				set i=i+by
			else
				set i=0
			endif
		endmethod
		static method get takes unit u returns thistype
			local thistype this = GetUnitIndex(u)
			if u != uArr[this] then
				set uArr[this] = u
				set i = 0
			endif
			return this
		endmethod
	endstruct
	/*
		struct Target textmacros;
		-all the textmacros require local thistype variable "this"
	*/
		// allocation, deallocates possible old instance overlapping
	//! textmacro UNITTARGET_TARGET_GET takes U
		set this = GetUnitIndex($U$)
		if uArr[this] != $U$ then // referred to old unit / instance is old
			if target != null then // old instance is still referenced as attacker
				call Attackers.get(target).shiftValue(-1) // remove attacker
			endif
			set lastOrder = 0
			set target = null
			set uArr[this] = $U$
		endif
	//! endtextmacro
		// delloaction when possible
	//! textmacro UNITTARGET_TARGET_LOSE takes U
		set this = GetUnitIndex($U$)
		if uArr[this] == $U$ then // if instance has been used by U
			if target != null then // instance is still referenced as attacker
				call Attackers.get(target).shiftValue(-1) // forget as attacker
			endif
				// null unused variables:
			set target = null
			set uArr[this] = null
		endif
	//! endtextmacro
		// fast target forget
	//! textmacro UNITTARGET_TARGET_FORGET
		if target != null then // instance is still referenced as attacker
			call Attackers.get(target).shiftValue(-1) // forget as attacker
		endif
		set target = null
	//! endtextmacro
		// apply target
	//! textmacro UNITTARGET_TARGET_APPLY takes U
		if target != $U$ then
			if target != null then
				call Attackers.get(target).shiftValue(-1) // forget as attacker
			endif
			set target = $U$
			call Attackers.get($U$).shiftValue(1) // reference as attacker
		endif
	//! endtextmacro
	struct Target extends array
		private static trigger tAcquire = CreateTrigger()
		private static trigger tForget = CreateTrigger()
		private static unit array uArr	// instance host
		private unit target
		private integer lastOrder
		static method operator [] takes unit u returns unit
			local thistype this = GetUnitIndex(u)
			if u != uArr[this] then
				set uArr[this] = u
				set target = null
			endif
			return target
		endmethod
		private static method onAcquire takes nothing returns boolean
			local unit u = GetTriggerUnit()
			local unit u2 = GetOrderTargetUnit()
			local thistype this
				// possible deallocation
			//! runtextmacro UNITTARGET_TARGET_GET("u")
				// get order
			set lastOrder = GetIssuedOrderId()
				// is target of attack an enemy?
			if IsUnitEnemy(u,GetOwningPlayer(u2)) then
					// is the order attack?
				if lastOrder==851971 /*=smart*/ or lastOrder==851983 /*=attack*/ then
						// proper attack+proper enemy > target=u2
					//! runtextmacro UNITTARGET_TARGET_APPLY("u2")
				else
						// not viable > target=null
					//! runtextmacro UNITTARGET_TARGET_FORGET()
				endif
			else
					// not viable > target=null
				//! runtextmacro UNITTARGET_TARGET_FORGET()
			endif
			set u = null
			set u2 = null
			return false
		endmethod
		private static method onHit takes nothing returns nothing
			local unit u = GetEventDamageSource()
			local unit u2
			local thistype this
				// get instance
			//! runtextmacro UNITTARGET_TARGET_GET("u")
				// compare order
			if lastOrder==851971 /*=smart*/ or lastOrder==851983 /*=attack*/ then
				set u2 = GetTriggerUnit()
				//! runtextmacro UNITTARGET_TARGET_APPLY("u2")
				set u2 = null
			endif
			set u = null
		endmethod
		private static method onForget takes nothing returns boolean
			local unit u = GetTriggerUnit()
			local thistype this
				// get instance
			//! runtextmacro UNITTARGET_TARGET_GET("u")
				// set order
			set lastOrder = GetIssuedOrderId()
				// null target
			//! runtextmacro UNITTARGET_TARGET_LOSE("u")
			set u = null
			return false
		endmethod
		private static method onInit takes nothing returns nothing
			call TriggerRegisterAnyUnitEventBJ(tAcquire,EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
			call TriggerAddCondition(tAcquire,function thistype.onAcquire)
			call TriggerRegisterAnyUnitEventBJ(tForget,EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
			call TriggerAddCondition(tForget,function thistype.onForget)
			call StructuredDD.addHandler(function thistype.onHit)
		endmethod
	endstruct
endlibrary
I've added a ton of comments to describe what I'm trying to do at each step, hope someone can help me :/

So currently the library doesn't work, the Attackers[unit] always returns 0 :/ damn...
 
Last edited by a moderator:

Deleted member 219079

D

Deleted member 219079

After long wait: Progress :D

What's new:
I made a library "Attackers" so that you can know whether there's any threat for your hero around.
You can download it here, requirement 1, requirement 2, requirement 3, along with PUI, StructuredDD and TimerUtils. I'm not yet utilizing this library, but I will in the future.

I updated the Scope at OP btw.

You can see that even if the Paladin's outnumbered (AI system issues the "onFallback" - event), it looks for juicy target.

But if the Paladin is on low stats and in danger to die, AI system will call "onLowLife" right away.

This may sound really matterless, but it makes the unit do decisions normal AI wouldn't.
 

Deleted member 219079

D

Deleted member 219079

Event is still just a concept, I sent it for edo to review on 14 of Decemember :/

If it turns out it's somehow a resource hog or takes very much memory, I'll have to rebuild the AI library, since there is no replacement for it..

If you're interested in reviewing plz say, kind of tired of waiting...
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,255
There needs to be 2 tiers of AI to control the hero.
1. Army, which controls the general movement of units. This could be an army, summons or even just a hero.
2. Tactical, which controls what a specific unit does in combat. Heroes would be more complex than summons to cope with their higher importance (cost, power etc) and more complex abilities.

The implementation for 1 is map dependent. In the case of melee it might be defend base, creep or attack enemy. In the case of an AoS it could be push lanes, assassinate heroes or buy items/heal. In the case of a hero defense it could simply be stand at a lane. It might even be novel such as for an RPG so a hero roams around randomly and appears to do stuff.

Two is the more complex part. It involves choosing the best targets, optimizing skill usage, managing mana resources, evading/dodging powerful persistents, re-positioning for optimum damage taken, choosing when to flee the army (if allowed) etc.

Above army there is a general "global" tier of thinking however this only applies to RTS maps or maps where the player manages a lot of units in different ways. It includes resource management, army composition construction, base management etc.

By dividing AI into these tiers you can modularize the implementation which would make it easier to develop and maintain.
 

Deleted member 219079

D

Deleted member 219079

@DSG You can see what kind of approach I've taken in the AI in the first scope. When certain conditions are met, the AI library queues a certain function to be ran. So the AI library itself won't make the decisions.

Update: I've just finished my first plugin for the HeroAI: SkillMod. SkillMod will take care of ability usage. At the moment it is pretty rough, but the code will be flexible enough to support Hashtable over Stack.

And I know ye pessimistic ppl are gonna hate me for doing this, but I had to create my own Unit Indexer. Basically I first thought of making my own changes to PUI as it would support my Event library, but then I wouldn't be able to publish that version of HeroAI here, as I would be publishing my own version of someone else's work. So I had to make Unit Indexer from scratch. Basically there's not anything else new than the fact that it can bind a function to be queued to run when instance gets deindexed, I'm already using this on my newest Attackers - library. Here's the UI at its current form: link. There's nothing groundbreaking there. But unlike PUI, its recycler won't go over unallocated instances. This is a plus as I can use fewer scans a second to achieve same recycle speed. Safe to say, you could do just fine with a clock rate of 0.10 too.

Why don't you just post it in the lab/resource/spell section if you want it to get reviewed?
Cuz I don't need to do that. Plus I'm not stupid enough to submit a system onto resource section. I'm working on a review by private. Hive's vJASS nerds enthusiastics just don't know what means "constructive criticism" without 99% of it being random insulting and bashing like "why would you do this", "you do know there's already a system for that", "how could this work", "what do you need this for", "what's wrong with xxx?", "the algorithm is very slow" etc.

As soon as I will get the judgement on Event, whether it's usable as a core resource, I will post HeroAI, SkillMod and the Library for SkillMod here. If it's not usable... I dunno D:
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,255
@DSG You can see what kind of approach I've taken in the AI in the first scope. When certain conditions are met, the AI library queues a certain function to be ran. So the AI library itself won't make the decisions.
Actually I cannot see anything seeing how you have only been posting random snippets in this thread.

I was just stating a general way to construct it. A problem I see is many people do not respect the AI tiers and instead do it all with a single loop leading to difficulties making decisions.
 

Deleted member 219079

D

Deleted member 219079

You can see how you are supposed to use the system in the OP.


@Xonok Ye units will make the undefend order on death, how am I supposed to take advantage on that? Also I think that trick is for UIs which index all the units.
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
You can see how you are supposed to use the system in the OP.


@Xonok Ye units will make the undefend order on death, how am I supposed to take advantage on that? Also I think that trick is for UIs which index all the units.

The tricks works so that when a unit is removed, then undefend is ordered twice. On the second order there unit type of the unit that was ordered is null, so the unit has already been removed.

Why would you ever have several unit indexers in a map? It's unnecessary and the performance drain of having one outweighs the drain of having several.
 

Deleted member 219079

D

Deleted member 219079

The tricks works so that when a unit is removed, then undefend is ordered twice. On the second order there unit type of the unit that was ordered is null, so the unit has already been removed.
Thanks for the guidance.

Why would you ever have several unit indexers in a map? It's unnecessary and the performance drain of having one outweighs the drain of having several.
I don't.

As an update, the "Paladin AI" - demo scope has been successfully finished, the result can bee seen from the OP. Now I'm moving onto:
>"Archmage AI" - demo scope
>"SummonMod" - plugin

SummonMod will make summoned units follow the hero. I'll need this mechanic with the Archmage.
 
Status
Not open for further replies.
Top