Larian Banner: Baldur's Gate Patch 9
Previous Thread
Next Thread
Print Thread
Page 1 of 3 1 2 3
Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
A request on Ameranth's and Elric's Epic Encounters Nexus site reminded me of some old wounds I still suffer:

Several times I tried to teach NPCs in recognizing items blocking their path and in removing them, both in combat and, not least, during their peace routines.
While it's mostly just 'fair' that the Evil gets trapped by the Good, I felt quite pity about those innocent NPCs that got locked in by dark-minded players, to be utterly dependent on their caprice. I tried to help them but, as I admit, I always failed.

This time, though, I seem to be on the right track and got a prototype working, both in combat and in peace, which made NPCs successfully free theirselves from their 'prisons' or remove items that stood between them and me.
They aren't 100% accurate (and never will be) and sometimes there are some weird side-effects (like throwing items through walls or against another NPC's head), not exactly knowing which item is the one to move and where to throw it, but they managed to deliver quite well under different circumstances (blocking items close to themselves, close to the target or somewhere in between).

I had three test situations:

1) I placed items around Mayor Cecil's chair, where he uses to sit half the day, and watched if he would be able to reach his chair, starting from the cupboard near the stairs:

He walked to the barricades I had built, took the candle holder, throw it against his wife's head (no more than 1 damage, by the way) and walked close to his chair. And since I had placed two additional chairs to block the path twice, he took one chair, threw it away (well, once he threw his own chair right into the next room) and had a seat as though nothing had happened. I walled him in again, while he was relaxing, but he delivered as well.

2) There's a small hut in my testlevel, with two crates unfortunately blocking the entrance (the previous owner left it this way). I placed my player character inside the hut and then called several NPCs to come to me. They moved to the walls, walked around to the entrance, removed one crate, opened the door (thanks to the engine) and got in.

3) I started a fight in Morris's kitchen, some guards came in, I walked right out the door and placed two barrels at the door. Once a similar obstacle as a dragon, this time they just threw one barrel out of their way and attacked me.

I'm quite confident that there are still flaws or situations they can't handle or need some time for, doing stupid things. But I think I can already show it to you guys, so you can do some tests with it.

I've scripted two effects to see where the character wants to move to but fails in doing so (and to see which part of the script is being executed). The blue light shows the original target of the character when the movement fails. The red light shows an approximate position the character wants to move to when the movement fails. When you see the red light the character will try to move items close to his own position or close to the red light position.

Some comments on the script:

The main problems of doing this are:

1) We don't know the possible (but blocked) paths to the target.
2) We don't know where the blocking items are.
3) We don't know which items to regard (and can't or shouldn't check for every possible item template).
4) The scripting isn't really built for this specific problem. There is no OnMovementFailed EVENT, it's only a INTERRUPT event. But we necessarily need this. Recording every item the player moves or drops is not viable. Always checking for possible items that are in the way doesn't work either (we don't know the path and which items block it). Just checking for items the character is facing would be absolutely unreliable.
So we have to add the OnMovementFailed lines to every reaction that contains movement (not much work for combat, but a lot of work for all NPCs of Main!).

So my idea was to move the character as close to the target as possible first. Since characters don't move at all when all paths are blocked, we don't know where we have to search for items. So I calculate a position half on the way to the target (slightly randomized, in case there's a wall or something) and let the character move there. If the movement fails the character will check for items he is facing (close to himself or close to the temporary target position) and move them, otherwise he will check for an even closer position and try to move there. He will ignore all items with weight less than 1000 and more than 40000 (I haven't checked all item weights yet, maybe there are a few that can block the path and are lighter than 1000/heavier than 40000). Maybe there will be conflicts with immobile items. In this case I will have to add more conditions. There could also be a problem with situations in which a character's movement fails for other reasons than items (don't know if this case exists).
But all in all I'm quite happy about how it works already.

Now, I hope it works for you and would be glad about your feedback!


My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
Here's the script for Cecil:

Code
#INCLUDE State_Manager

INIT
USING State_Manager
CHARACTER:__Me
CHARACTER:%Victoria = CYS_OrcLibrarian_6a429053-8eb7-48d1-94cc-7292449487d7
TRIGGER:%pointBook = CYS_PointCecil1_faad94a4-5ae5-45bc-9b71-31510bc6cef5
TRIGGER:%pointCloset = CYS_Point_Cecil_Closet_37dac4f4-48e2-4ae7-b34d-349bf5273287
TRIGGER:%pointOutside = CYS_PointOrcLibOutside_e6945537-cff1-4699-b3f9-5d2a47ceed9d
ITEM:%cupboard = FUR_Rich_Cupboard_A_Door_A_002_ee9c92f9-ff31-41e9-965e-0128bf72d6b2
FLOAT3:%TargetPos
FLOAT3:%TempTarget

BEHAVIOUR	

REACTION State_ActionManager, 0
USAGE PEACE
ACTIONS
	GetWeightedRandom(%currentAction, "Action_ReadBook", INT:10, "Action_TalkToGuardsOutside", INT:4, "Action_LookIntoCloset", INT:4)
	SetPriority(%currentAction, 1100)

REACTION State_VictoriaDead, 0
USAGE PEACE
VARS
	FIXEDSTRING:_Animation
ACTIONS
	CharacterMoveTo(%Victoria,1)
	CharacterLookAt(%Victoria)
	GetWeightedRandom(_Animation,"Crying_01",FLOAT:15,"Kneel_01",FLOAT:10,"Kneel_02",FLOAT:10,"Depressed_01",FLOAT:5)
	CharacterPlayAnimation(_Animation)
	Sleep(4)
	DialogStart("CYS_AD_CecilMourning",__Me)

REACTION Action_ReadBook, 0
USAGE PEACE
VARS
	FLOAT3:_Pos
ACTIONS
	IF "!c1"
		CharacterHasStatus(__Me,SITTING)
	THEN
		CharacterMoveTo(%pointBook, 0)
		CharacterUseItem(CYS_CecilChair_acb98a79-1842-48ab-a86d-0fd1e177abaf)
	ENDIF
	CharacterPlayAnimation("Idle1")
	CharacterPlayAnimation("Idle3")
	Sleep(3)
	CharacterPlayAnimation("Idle2")
	SetPriority(%currentAction, 0)
INTERRUPT
Reset()
ON
		OnMovementFailed(_Pos)
	ACTIONS
	Set(%TargetPos,_Pos)
	PlayEffectAt(%TargetPos,"FX_Env_Fire_Blue_B")
	SetPriority("GetClose",2000)
	
REACTION Action_TalkToGuardsOutside, 0
USAGE PEACE
ACTIONS
	CharacterMoveTo(%pointOutside, 0)
	CharacterLookFrom(%pointOutside)
	Sleep(4)
	SetPriority(%currentAction, 0)
	
REACTION Action_LookIntoCloset, 0
USAGE PEACE
VARS
	FLOAT3:_Pos
ACTIONS
	CharacterMoveTo(%pointCloset, 0)
	CharacterLookFrom(%pointCloset)
	CharacterPlayAnimation("Fidget_Forward_01")
	ItemOpen(%cupboard)
	CharacterPlayAnimation("Look_Left_Short_01")
	CharacterPlayAnimation("Look_Right_Short_01")
	ItemClose(%cupboard)
	SetPriority(%currentAction, 0)
INTERRUPT
Reset()
ON
		OnMovementFailed(_Pos)
	ACTIONS
	Set(%TargetPos,_Pos)
	PlayEffectAt(%TargetPos,"FX_Env_Fire_Blue_B")
	SetPriority("GetClose",2000)
	
REACTION GetClose, 0
USAGE PEACE
VARS
	FLOAT:_distance
	FLOAT:_MyX
	FLOAT:_MyZ
	FLOAT:_TargetX
	FLOAT:_TargetZ
	FLOAT:_AttemptCounter = 0
	FLOAT:_AddX
	FLOAT:_AddZ
	FLOAT:_Divisor
	FLOAT3:_Target
	FLOAT3:_MyPos
ACTIONS
	Sleep(1)
	GetPosition(__Me,_MyPos)
	Set(%TempTarget,%TargetPos)
	IF "c1&c2&c3&c4&c5"
		GetX(_MyPos,_MyX)
		GetZ(_MyPos,_MyZ)
		GetX(%TargetPos,_TargetX)
		GetZ(%TargetPos,_TargetZ)
		GetDistance(_distance,__Me,%TargetPos)
	THEN
		Subtract(_TargetX,_MyX)
		Subtract(_TargetZ,_MyZ)
		GetRandom(_Divisor,1.8,1.9,2,2.1,2.2)
		Divide(_TargetX,_Divisor)
		Divide(_TargetZ,_Divisor)
		Add(_MyX,_TargetX)
		Add(_MyZ,_TargetZ)
		SetX(%TempTarget,_MyX)
		SetZ(%TempTarget,_MyZ)
		CharacterLookAt(%TempTarget)
		IF "c1"
			IsGreaterThen(_AttemptCounter,4)
		THEN
			Set(_AttemptCounter,0)
			SetPriority("MoveItems",0)
			CharacterEndTurn()
			SetPriority("GetClose",0)
		ELIF "c1"
			IsLessThen(_distance,4)
		THEN
			CharacterMoveTo(%TargetPos,0,0,0,0.2,1)
			SetPriority("GetClose",0)
		ELIF "!c1"
			IsLessThen(_distance,4)
		THEN
			CharacterMoveTo(%TempTarget,0,0,0,0.2,1)
			SetPriority("MoveItems",0)
		ELSE
			Set(_AttemptCounter,0)
			SetPriority("MoveItems",0)
			SetPriority("GetClose",0)
		ENDIF
	ENDIF
INTERRUPT
	Reset()
	ON
		OnMovementFailed(_)
	ACTIONS
		PlayEffectAt(%TempTarget,"FX_Env_Fire_Red_A")
		Add(_AttemptCounter,1)
		SetPriority("MoveItems",3000)
		SetPriority("GetClose",0)
		
REACTION MoveItems, 0
USAGE PEACE
VARS
	ITEM:_Item
	ITEM:_IgnoreItem1
	ITEM:_IgnoreItem2
	ITEM:_IgnoreItem3
	FLOAT:_Weight
	FLOAT:_X
	FLOAT:_Z
	FLOAT:_MyPosX
	FLOAT:_MyPosZ
	FLOAT3:_MyPos
CHECK "(c1&c2&c3&c4&!c5&c6)|(c7&c8&c9&!c10&c11)"
	ItemGet(_Item,__Me,6,Random,Distance,"")
	IsFacing(__Me,_Item)
	CanSee(__Me,_Item)
	ItemGetStat(_Weight,_Item,Weight)
	IsLessThen(_Weight,1000)
	IsLessThen(_Weight,40001)
	ItemGet(_Item,%TempTarget,6,Random,Distance,"")
	IsFacing(__Me,_Item)
	ItemGetStat(_Weight,_Item,Weight)
	IsLessThen(_Weight,1000)
	IsLessThen(_Weight,40001)
ACTIONS
	IF "!c1"
		ItemIsDestroyed(_Item)
	THEN
		CharacterMoveTo(_Item)
	ENDIF
	GetPosition(__Me,_MyPos)
	GetRandom(_X,-3,-2,2,3)
	GetRandom(_Z,-3,-2,2,3)
	IF "c1&c2"
		GetX(_MyPos,_MyPosX)
		GetZ(_MyPos,_MyPosZ)
	THEN
		Subtract(_MyPosX,_X)
		Subtract(_MyPosZ,_Z)
		SetX(_MyPos,_MyPosX)
		SetZ(_MyPos,_MyPosZ)
	ENDIF
	IF "!c1"
		ItemIsDestroyed(_Item)
	THEN
		CharacterMoveItem(_Item,_MyPos)
	ENDIF
	SetPriority("MoveItems",0)
INTERRUPT
	Reset()
	SetPriority("MoveItems",0)	
	
	


My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
And the DefaultCharacter script, for combat tests:

Code
#INCLUDE Base
INIT
	USING Base
	CHARACTER:__Me
	CHARACTER:%DialogLookAtChar=null
	FLOAT3:%PeaceReturnPosition=null
	FLOAT:%setTargetDefaultBestScore=10000
	INT:%defaultEvaluateTarget=1
	INT:%EvaluateScores=0
	FLOAT3:%TargetPos
	FLOAT3:%TempTarget

EVENTS

EVENT TauntedByChar
VARS
	CHARACTER:_Char
ON
	OnCharacterStatus(__Me,TAUNTED)
ACTIONS
	IF "c1"
		CharacterGetStatusSourceCharacter(__Me,TAUNTED,_Char)
	THEN
		Set(%defaultEvaluateTarget,0)
		CharacterSetEnemy(__Me,_Char)
	ELSE
		CharacterRemoveStatus(__Me,TAUNTED)
	ENDIF
	
EVENT TauntedFinished
ON
	OnCharacterStatusRemoved(__Me,TAUNTED)
ACTIONS
	CharacterSetEnemy(__Me,null)
	Set(%defaultEvaluateTarget,1)

EVENT FallbackIterateTarget
VARS
	CHARACTER:_Char
	FLOAT:_targetDistance
	FLOAT:_targetClosestThisScore=0	
ON
	OnIterateCharacter(_Char,"FallbackTargetClosestIterator")
ACTIONS
	IF "!c1&!c2&c3&!c4&!c5&c6"
		CharacterIsDead(_Char)
		IsEqual(__Me,_Char)
		CharacterIsEnemy(__Me,_Char)
		CharacterHasStatus(_Char,INVISIBLE)
		CharacterHasStatus(_Char,SNEAKING)
		GetDistance(_targetDistance,__Me,_Char)
	THEN
		IF "c1&!c2"
			CharacterHasTalent(_Char,Stench)
			CharacterHasStatus(_Char,AGGRO_MARKED)
		THEN
			Multiply(_targetDistance,4)
		ENDIF
		IF "c1&c2"
			CharacterHasTalent(_Char,Stench)
			CharacterHasStatus(_Char,AGGRO_MARKED)
		THEN
			Multiply(_targetDistance,2)
		ENDIF
		Set(_targetClosestThisScore,_targetDistance)
		IF "!c1"
			IsGreaterThen(_targetClosestThisScore,%setTargetDefaultBestScore)
		THEN
			Set(%setTargetDefaultBestScore,_targetClosestThisScore)
			CharacterSetEnemy(__Me,_Char)
		ENDIF
	ENDIF
		
EVENT DefaultIterateSetTarget
VARS 
	CHARACTER:_Char
	CHARACTER:_previousSetTarget
	FLOAT:_targetDistance
	INT:_alreadyTargetedBy
	FLOAT:_targetClosestThisScore=0
	FLOAT:_movement
ON
	OnIterateCharacter(_Char,"DefaultSetTargetClosestIterator")
ACTIONS
	IF "!c1&!c2&c3&c4&c5&!c6&!c7"
		CharacterIsDead(_Char)
		IsEqual(__Me,_Char)
		CharacterIsEnemy(__Me,_Char)
		GetDistance(_targetDistance,__Me,_Char)
		CharacterGetHostileCount(_alreadyTargetedBy,_Char)
		CharacterHasStatus(_Char,INVISIBLE)
		CharacterHasStatus(_Char,SNEAKING)
	THEN
		IF "c1&c2"
			CharacterGetEnemy(_previousSetTarget,__Me)
			IsEqual(_previousSetTarget,_Char)
		THEN
			Multiply(_targetDistance,0.5)
		ENDIF
		IF "c1&!c2&!c3"
			IsInDangerousSurface(_Char)
			CharacterIsFloating(__Me)
			CharacterHasStatus(__Me,ETHEREAL_SOLES)
		THEN
			Multiply(_targetDistance,2)
		ENDIF
		IF "c1&(c2|c3)"
			IsInDangerousSurface(_Char)
			CharacterIsFloating(__Me)
			CharacterHasStatus(__Me,ETHEREAL_SOLES)
		THEN
			Multiply(_targetDistance,0.8)
		ENDIF
		IF "c1"
			CharacterIsSummon(_Char)
		THEN
			Multiply(_targetDistance,1.5)
		ENDIF
		IF "c1"
			CharacterHasStatus(_Char,AGGRO_MARKED)
		THEN
			Multiply(_targetDistance,0.2)
		ENDIF
		IF "c1"
			IsEqual(_alreadyTargetedBy,0)
		THEN
			Multiply(_targetDistance,0.5)
		ENDIF
		IF "c1"
			IsEqual(_alreadyTargetedBy,1)
		THEN
			Multiply(_targetDistance,0.6)
		ENDIF
		IF "c1&!c2"
			IsGreaterThen(_alreadyTargetedBy,1)
			CharacterHasStatus(_Char,AGGRO_MARKED)
		THEN
			Add(_alreadyTargetedBy,0.6)
			Multiply(_alreadyTargetedBy,_alreadyTargetedBy)
			Multiply(_targetDistance,_alreadyTargetedBy)
		ENDIF
		IF "c1&!c2"
			IsGreaterThen(_alreadyTargetedBy,3)
			CharacterHasStatus(_Char,AGGRO_MARKED)
		THEN
			Multiply(_targetDistance,2)
		ENDIF
		IF "!c1"
			CharacterCanSee(__Me,_Char)
		THEN
			Multiply(_targetDistance,3)
		ENDIF
		IF "c1&!c2"
			CharacterHasTalent(_Char,Stench)
			CharacterHasStatus(_Char,AGGRO_MARKED)
		THEN
			Multiply(_targetDistance,4)
		ENDIF
		IF "c1&c2"
			CharacterHasTalent(_Char,Stench)
			CharacterHasStatus(_Char,AGGRO_MARKED)
		THEN
			Multiply(_targetDistance,2)
		ENDIF
		Set(_targetClosestThisScore,_targetDistance)
		IF "!c1"
			IsGreaterThen(_targetClosestThisScore,%setTargetDefaultBestScore)
		THEN
			Set(%setTargetDefaultBestScore,_targetClosestThisScore)
			CharacterSetEnemy(__Me,_Char)
		ENDIF
	ENDIF

EVENT StartCombatFetchPos
ON
	OnCombatStarted()
ACTIONS
	CharacterSetEnemy(__Me,null)
	Set(%defaultEvaluateTarget,1)
	IF "c1" 
		IsEqual(%PeaceReturnPosition,null)
	THEN
		GetPosition(__Me,%PeaceReturnPosition)
		CharacterForceUpdate(1)
	ENDIF
	
EVENT ReturnToPosOnCombatEnded
ON
	OnCombatEnded()
ACTIONS
	IF "c1|c2" 
		CharacterIsInParty(__Me)
		CharacterIsDead(__Me)
	THEN
		CharacterEvent(__Me,"ClearPeaceReturn")
	ELSE
		SetHealth(__Me,1)
	ENDIF

EVENT StoryOverridePeaceReturn
ON 
	OnStoryOverride()
	OnCharacterEvent(__Me,"ClearPeaceReturn")
ACTIONS
	IF "!c1"
		IsEqual(%PeaceReturnPosition,null)
	THEN
		Set(%PeaceReturnPosition,null)
		Interrupt("ReturnToPeacePosition")
		CharacterForceUpdate(0)
	ENDIF
	
EVENT StartEvaluateScore
ON
	OnFunction("StartEvaluateScore")
ACTIONS
	IF "c1&c2"
		IsEqual(%EvaluateScores,0)
		CharacterIsInActiveTurn(__Me)
	THEN	
		CallFunction("CalculateScore")
		StartTimer("CalcScoreTimer",0.2,-1)
		Set(%EvaluateScores,1)
	ENDIF
	
EVENT StopEvaluateScore
ON
	OnFunction("StopEvaluateScore")
ACTIONS
	IF "c1"
		IsEqual(%EvaluateScores,1)
	THEN
		StopTimer("CalcScoreTimer")
		Set(%EvaluateScores,0)
	ENDIF
	
EVENT DontAttackAlliesOrInvisibles // to solve charmed chars getting back to normal but still targeting as if they were charmed
VARS
	CHARACTER:_Target
ON
	OnTurn()
ACTIONS
	IF "c1&(c2|c3)"
		CharacterGetEnemy(_Target,__Me)
		CharacterIsAlly(__Me,_Target)
		CharacterHasStatus(_Target,INVISIBLE)
	THEN
		CharacterSetEnemy(__Me,null)
		Set(%defaultEvaluateTarget,1)
	ENDIF
	
EVENT ClearTauntedWhenTaunterDies
VARS
	CHARACTER:_EventChar=null
	CHARACTER:_Taunter=null
ON
	OnDie(_EventChar,_,_,_)
ACTIONS
	IF "c1&c2&c3"
		CharacterHasStatus(__Me,TAUNTED)
		CharacterGetStatusSourceCharacter(__Me,TAUNTED,_Taunter)
		IsEqual(_Taunter,_EventChar)
	THEN
		CharacterRemoveStatus(__Me,TAUNTED)
	ENDIF
	
EVENT ClearSetEnemy
VARS
	CHARACTER:_Char
	CHARACTER:_EventChar
ON
	OnDie(_EventChar,_,_,_)
	OnCharacterStatus(_EventChar,INVISIBLE)
	OnCharacterStatus(_EventChar,CHARMED)
	OnCharacterStatus(_EventChar,SNEAKING)
ACTIONS
	IF "c1&c2"
		CharacterGetEnemy(_Char,__Me)
		IsEqual(_Char,_EventChar)
	THEN
		CharacterSetEnemy(__Me,null)
		Set(%defaultEvaluateTarget,1)
	ENDIF	

EVENT ForceNewEnemyWhenCharmed
ON
	OnCharacterStatus(__Me,CHARMED)
ACTIONS
	CharacterSetEnemy(__Me,null)
	Set(%defaultEvaluateTarget,1)
	
EVENT EvaluateTargets
ON
	OnTurn()
ACTIONS
	IF "!c1"
		CharacterHasStatus(__Me,TAUNTED)
	THEN
		SetPriority("Combat_DefaultChooseEnemy",9)
		SetPriority("Combat_NoSetEnemy",6)
		Set(%setTargetDefaultBestScore,10000)
		Set(%defaultEvaluateTarget,1)
		CallFunction("StartEvaluateScore")
		SetPriority("Combat_WhenInvisible_RunAway",10)
	ENDIF
	
EVENT KillScoreTimer
ON
	OnTurnEnded()
	OnCombatEnded()
ACTIONS
	CallFunction("StopEvaluateScore")
	
EVENT CalcScoreTimer
ON
	OnTimer("CalcScoreTimer")
ACTIONS
	CallFunction("CalculateScore")

//----	
BEHAVIOUR
REACTION ReturnToPeacePosition,15000
USAGE PEACE
CHECK "!c1"
	IsEqual(%PeaceReturnPosition,null)
ACTIONS
	CharacterMoveTo(%PeaceReturnPosition,1,1,1,0)
	CharacterEvent(__Me,"ClearPeaceReturn")

REACTION Combat_WhenInvisible_RunAway, 10
USAGE COMBAT
VARS
	FLOAT:_MyVitality
CHECK "c1"
	CharacterHasStatus(__Me,"INVISIBLE")
ACTIONS
	IF "(c1&c2)|c3"
		CharacterGetStat(_MyVitality,__Me,Vitality)
		IsLessThen(_MyVitality,0.5)
		IsRandom(0.5)
	THEN
		CharacterFleeFrom(Enemy,10)
	ELSE
		SetPriority("Combat_WhenInvisible_RunAway",0)
	ENDIF
INTERRUPT
ON
	OnMovementFailed(_)
ACTIONS
	CharacterEndTurn()	
	
REACTION Combat_DefaultChooseEnemy, 9
USAGE COMBAT
VARS
	CHARACTER:_Enemy
CHECK "c1|!c2"
	IsEqual(%defaultEvaluateTarget,1)
	CharacterGetEnemy(_Enemy,__Me)
ACTIONS
	Set(%setTargetDefaultBestScore,10000)
	IterateCharactersInCombat(__Me,"DefaultSetTargetClosestIterator")
	IF "c1"
		CharacterGetEnemy(_Enemy,__Me)
	THEN
		Set(%defaultEvaluateTarget,0)
	ELSE
		SetPriority("Combat_DefaultChooseEnemy",0)
	ENDIF

REACTION Combat_AttackSetEnemy, 8
USAGE COMBAT
VARS
	CHARACTER:_Enemy
	FLOAT:_dist
	FLOAT:_minRange
	FLOAT:_maxRange
	FLOAT3:_Pos
CHECK "c1&c2&c3&(c4|(c5&!c6&!c7))"
	CharacterGetEnemy(_Enemy,__Me) // returns false if null
	CharacterGetWeaponRange(_minRange,_maxRange,__Me)
	GetInnerDistance(_dist,__Me,_Enemy)
	CharacterCanSee(__Me,_Enemy)
	IsLessThen(_dist,_maxRange)
	CharacterHasWeaponType(__Me,Bow,1)
	CharacterHasWeaponType(__Me,CrossBow,1)
ACTIONS
	CharacterAttack(_Enemy)
INTERRUPT
ON
		OnMovementFailed(_Pos)
	ACTIONS
	Set(%TargetPos,_Pos)
	PlayEffectAt(%TargetPos,"FX_Env_Fire_Blue_B")
	SetPriority("GetClose",2000)

REACTION Combat_AttackSetEnemyWithBowHack, 8
USAGE COMBAT
VARS
	CHARACTER:_Enemy
	FLOAT:_dist
	FLOAT:_minRange
	FLOAT:_maxRange
	FLOAT3:_Pos
CHECK "c1&c2&c3&!c4&c5&(c6|c7)"
	CharacterGetEnemy(_Enemy,__Me) // returns false if null
	CharacterGetWeaponRange(_minRange,_maxRange,__Me)
	GetInnerDistance(_dist,__Me,_Enemy)
	CharacterCanSee(__Me,_Enemy)
	IsLessThen(_dist,2)
	CharacterHasWeaponType(__Me,Bow,1)
	CharacterHasWeaponType(__Me,CrossBow,1)
ACTIONS
	CharacterAttack(_Enemy)
INTERRUPT
ON
		OnMovementFailed(_Pos)
	ACTIONS
	Set(%TargetPos,_Pos)
	PlayEffectAt(%TargetPos,"FX_Env_Fire_Blue_B")
	SetPriority("GetClose",2000)

REACTION Combat_MoveToSetEnemy, 7
USAGE COMBAT
VARS
	CHARACTER:_Enemy
	FLOAT:_minRange
	FLOAT:_maxRange
	FLOAT3:_Pos
CHECK "c1&!c2&c3"
	CharacterGetEnemy(_Enemy,__Me)
	CharacterCanSee(__Me,_Enemy)
	CharacterGetWeaponRange(_minRange,_maxRange,__Me)
ACTIONS	
	CharacterMoveTo(_Enemy,1,0,0,_minRange,_maxRange)
INTERRUPT
ON
		OnMovementFailed(_Pos)
	ACTIONS
	Set(%TargetPos,_Pos)
	PlayEffectAt(%TargetPos,"FX_Env_Fire_Blue_B")
	SetPriority("GetClose",2000)
	
REACTION Combat_NoSetEnemy, 6
USAGE COMBAT
VARS
	CHARACTER:_Char
CHECK "!c1"
	CharacterGetEnemy(_Char,__Me)
ACTIONS	
	IterateCharactersInCombat(__Me,"FallbackTargetClosestIterator")
	IF "!c1"
		CharacterGetEnemy(_Char,__Me)
	THEN
		SetPriority("Combat_NoSetEnemy",0)
	ENDIF
	
REACTION Combat_MoveOutOfSurfaces, 5
USAGE COMBAT
CHECK "(c1|c2)&!c3&!c4"
	IsInSurface(__Me,Oil)
	IsInDangerousSurface(__Me)
	CharacterIsFloating(__Me)
	CharacterHasStatus(__Me,ETHEREAL_SOLES)
ACTIONS
	IF "c1"
		IsInSurface(__Me,Oil)
	THEN
		CharacterFleeFromSurface(Oil)
	ELIF "c1"
		IsInDangerousSurface(__Me)
	THEN
		CharacterFleeFromDangerousSurface()
	ENDIF
INTERRUPT
ON
	OnException()
ACTIONS
	DelayReaction("Combat_MoveOutOfSurfaces",6)
	
REACTION Combat_TakeCover, 2
USAGE COMBAT
VARS	
	FLOAT3:_Pos	
	FLOAT:_Value
CHECK "c1&c2&c3&c4&c5"	
	CharacterGetStat(_Value,__Me,Vitality)
	IsLessThen(_Value,1)
	FindCover(_Pos,__Me,__Me,20)
	GetDistance(_Value,_Pos,__Me)
	IsGreaterThen(_Value,1)
ACTIONS
	CharacterPlayEffect(__Me,"FX_GP_QuestionMark_A")
	CharacterEndTurn()
INTERRUPT
ON
	OnMovementFailed(_)
ACTIONS
	DelayReaction("Combat_TakeCover",3)
	
REACTION Combat_NoReactionFound, 1
USAGE COMBAT
ACTIONS
	//CharacterPlayEffect(__Me,"FX_GP_QuestionMark_A")
	CharacterEndTurn()
	
REACTION AutomatedDialog_Interrupt, 1700
USAGE PEACE
CHECK "c1&!c2"
	IsInDialog(__Me)
	CharacterIsPlayer(__Me)
ACTIONS
	IF "!c1"
		IsEqual(%DialogLookAtChar,null)
	THEN
		CharacterLookAt(%DialogLookAtChar,0)
	ENDIF
	Sleep(2.0)
INTERRUPT
	Set(%DialogLookAtChar,null)	
	
REACTION GetClose, 0
USAGE PEACE
USAGE COMBAT
VARS
	FLOAT:_distance
	FLOAT:_MyX
	FLOAT:_MyZ
	FLOAT:_TargetX
	FLOAT:_TargetZ
	FLOAT:_AttemptCounter = 0
	FLOAT:_AddX
	FLOAT:_AddZ
	FLOAT:_Divisor
	FLOAT3:_Target
	FLOAT3:_MyPos
ACTIONS
	Sleep(1)
	GetPosition(__Me,_MyPos)
	Set(%TempTarget,%TargetPos)
	IF "c1&c2&c3&c4&c5"
		GetX(_MyPos,_MyX)
		GetZ(_MyPos,_MyZ)
		GetX(%TargetPos,_TargetX)
		GetZ(%TargetPos,_TargetZ)
		GetDistance(_distance,__Me,%TargetPos)
	THEN
		Subtract(_TargetX,_MyX)
		Subtract(_TargetZ,_MyZ)
		GetRandom(_Divisor,1.8,1.9,2,2.1,2.2)
		Divide(_TargetX,_Divisor)
		Divide(_TargetZ,_Divisor)
		Add(_MyX,_TargetX)
		Add(_MyZ,_TargetZ)
		SetX(%TempTarget,_MyX)
		SetZ(%TempTarget,_MyZ)
		CharacterLookAt(%TempTarget)
		IF "c1"
			IsGreaterThen(_AttemptCounter,4)
		THEN
			Set(_AttemptCounter,0)
			SetPriority("MoveItems",0)
			CharacterEndTurn()
			SetPriority("GetClose",0)
		ELIF "c1" //elif block added
			IsLessThen(_distance,4)
		THEN			
			CharacterMoveTo(%TargetPos,0,0,0,0.2,1)
			SetPriority("GetClose",0)
		ELIF "!c1"
			IsLessThen(_distance,4)
		THEN			
			CharacterMoveTo(%TempTarget,0,0,0,0.2,1)
			SetPriority("MoveItems",0)
		ELSE
			Set(_AttemptCounter,0)
			SetPriority("MoveItems",0)
			SetPriority("GetClose",0)
		ENDIF
	ENDIF
INTERRUPT
	Reset()
	ON
		OnMovementFailed(_)
	ACTIONS
		PlayEffectAt(%TempTarget,"FX_Env_Fire_Red_A")
		Add(_AttemptCounter,1)
		SetPriority("MoveItems",3000)
		SetPriority("GetClose",0)
		
REACTION MoveItems, 0
USAGE PEACE
USAGE COMBAT
VARS
	ITEM:_Item
	ITEM:_IgnoreItem1
	ITEM:_IgnoreItem2
	ITEM:_IgnoreItem3
	FLOAT:_Weight
	FLOAT:_X
	FLOAT:_Z
	FLOAT:_MyPosX
	FLOAT:_MyPosZ
	FLOAT3:_MyPos
CHECK "(c1&c2&c3&c4&!c5&c6)|(c7&c8&c9&!c10&c11)"
	ItemGet(_Item,__Me,6,Random,Distance,"")
	IsFacing(__Me,_Item)
	CanSee(__Me,_Item)
	ItemGetStat(_Weight,_Item,Weight)
	IsLessThen(_Weight,1000)
	IsLessThen(_Weight,40001)
	ItemGet(_Item,%TempTarget,6,Random,Distance,"")
	IsFacing(__Me,_Item)
	ItemGetStat(_Weight,_Item,Weight)
	IsLessThen(_Weight,1000)
	IsLessThen(_Weight,40001)
ACTIONS
	IF "!c1"
		ItemIsDestroyed(_Item)
	THEN
		CharacterMoveTo(_Item)
	ENDIF
	GetPosition(__Me,_MyPos)
	GetRandom(_X,-3,-2,2,3)
	GetRandom(_Z,-3,-2,2,3)
	IF "c1&c2"
		GetX(_MyPos,_MyPosX)
		GetZ(_MyPos,_MyPosZ)
	THEN
		Subtract(_MyPosX,_X)
		Subtract(_MyPosZ,_Z)
		SetX(_MyPos,_MyPosX)
		SetZ(_MyPos,_MyPosZ)
	ENDIF
	IF "!c1"
		ItemIsDestroyed(_Item)
	THEN
		CharacterMoveItem(_Item,_MyPos)
	ENDIF
	SetPriority("MoveItems",0)
INTERRUPT
	Reset()
	SetPriority("MoveItems",0)


And here the whole code I added to both scripts:
Code

//for the INIT section:

FLOAT3:%TargetPos
FLOAT3:%TempTarget

//To be added to every reaction that contains movement (very annoying, but there is no OnMovementFailed EVENT):

VARS
FLOAT3:_Pos

INTERRUPT
Reset()
ON
	OnMovementFailed(_Pos)
ACTIONS
	Set(%TargetPos,_Pos)
	PlayEffectAt(%TargetPos,"FX_Env_Fire_Blue_B")
	SetPriority("GetClose",2000)
	
//Reaction to get closer to the original target:

REACTION GetClose, 0
USAGE PEACE
VARS
	FLOAT:_distance
	FLOAT:_MyX
	FLOAT:_MyZ
	FLOAT:_TargetX
	FLOAT:_TargetZ
	FLOAT:_AttemptCounter = 0
	FLOAT:_AddX
	FLOAT:_AddZ
	FLOAT:_Divisor
	FLOAT3:_Target
	FLOAT3:_MyPos
ACTIONS
	Sleep(1)
	GetPosition(__Me,_MyPos)
	Set(%TempTarget,%TargetPos)
	IF "c1&c2&c3&c4&c5"
		GetX(_MyPos,_MyX)
		GetZ(_MyPos,_MyZ)
		GetX(%TargetPos,_TargetX)
		GetZ(%TargetPos,_TargetZ)
		GetDistance(_distance,__Me,%TargetPos)
	THEN
		Subtract(_TargetX,_MyX)
		Subtract(_TargetZ,_MyZ)
		GetRandom(_Divisor,1.8,1.9,2,2.1,2.2)
		Divide(_TargetX,_Divisor)
		Divide(_TargetZ,_Divisor)
		Add(_MyX,_TargetX)
		Add(_MyZ,_TargetZ)
		SetX(%TempTarget,_MyX)
		SetZ(%TempTarget,_MyZ)
		CharacterLookAt(%TempTarget)
		IF "c1"
			IsGreaterThen(_AttemptCounter,4)
		THEN
			Set(_AttemptCounter,0)
			SetPriority("MoveItems",0)
			CharacterEndTurn()
			SetPriority("GetClose",0)
		ELIF "c1" //elif block added
			IsLessThen(_distance,4)
		THEN
			CharacterMoveTo(%TargetPos,0,0,0,0.2,1)
			SetPriority("GetClose",0)
		ELIF "!c1"
			IsLessThen(_distance,4)
		THEN
			CharacterMoveTo(%TempTarget,0,0,0,0.2,1)
			SetPriority("MoveItems",0)
		ELSE
			Set(_AttemptCounter,0)
			SetPriority("MoveItems",0)
			SetPriority("GetClose",0)
		ENDIF
	ENDIF
INTERRUPT
	Reset()
	ON
		OnMovementFailed(_)
	ACTIONS
		PlayEffectAt(%TempTarget,"FX_Env_Fire_Red_A")
		Add(_AttemptCounter,1)
		SetPriority("MoveItems",3000)
		SetPriority("GetClose",0)
	
//Reaction to detect and move items:
	
REACTION MoveItems, 0
USAGE PEACE
VARS
	ITEM:_Item
	FLOAT:_Weight
	FLOAT:_X
	FLOAT:_Z
	FLOAT:_MyPosX
	FLOAT:_MyPosZ
	FLOAT3:_MyPos
CHECK "(c1&c2&c3&c4&!c5&c6)|(c7&c8&c9&!c10&c11)"
	ItemGet(_Item,__Me,6,Random,Distance,"")
	IsFacing(__Me,_Item)
	CanSee(__Me,_Item)
	ItemGetStat(_Weight,_Item,Weight)
	IsLessThen(_Weight,1000)
	IsLessThen(_Weight,40001)
	ItemGet(_Item,%TempTarget,6,Random,Distance,"")
	IsFacing(__Me,_Item)
	ItemGetStat(_Weight,_Item,Weight)
	IsLessThen(_Weight,1000)
	IsLessThen(_Weight,40001)
ACTIONS
	IF "!c1"
		ItemIsDestroyed(_Item)
	THEN
		CharacterMoveTo(_Item)
	ENDIF
	GetPosition(__Me,_MyPos)
	GetRandom(_X,-3,-2,2,3)
	GetRandom(_Z,-3,-2,2,3)
	IF "c1&c2"
		GetX(_MyPos,_MyPosX)
		GetZ(_MyPos,_MyPosZ)
	THEN
		Subtract(_MyPosX,_X)
		Subtract(_MyPosZ,_Z)
		SetX(_MyPos,_MyPosX)
		SetZ(_MyPos,_MyPosZ)
	ENDIF
	IF "!c1"
		ItemIsDestroyed(_Item)
	THEN
		CharacterMoveItem(_Item,_MyPos)
	ENDIF
	SetPriority("MoveItems",0)
INTERRUPT
	Reset()
	SetPriority("MoveItems",0)
	


My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Dec 2016
Location: United States
member
Offline
member
Joined: Dec 2016
Location: United States
Very nice work! I did something similar (and much less complex) with Epic Encounters' last patch, allowing enemies to identify and destroy Ice Walls when they are blocked by them. I wish I still had the time to dive into this topic, because you've given us a solid starting point with this.

Some thoughts:

- One could use FindValidPosition() to detect if the moved item is being moved to an open, pathable space. Though, I don't remember if you can pass an item to this function.

- It is possible to check for items/characters throughout the line of travel that the moved item will traverse, and one could modify how the item is moved based on this feedback. This could be done by iterating items/characters at remote points, offset in increments from the item location to its destination.

- Character movement can fail if it is blocked by other characters.

- Ice Walls can block movement but cannot be moved.

- Weights of items would probably need to be researched/changed to fit into a categorization as you've considered already; as you've said, it's certainly unreasonable to check against numerous roots/stats.

- A less impressive/less lore-friendly (but potentially easier) method of solving this problem would be to script in teleportation for these characters whenever they are stuck. FindValidPosition() would work for this almost entirely on its own.

Last edited by Ameranth; 13/02/17 07:13 PM.
Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
Thanks for your response!

1) Have you ever successfully used 'FindValidPosition' in a script? It never did anything when I used it (it's supposed to work for items and characters).

2) I'll maybe check for characters close to the target position (though it's actually funny as it is). But I can't think of a way to check for walls without limiting the character too much with 'CanSee'. Actually the engine automatically checks for valid positions when NPCs move items but doesn't seem to check for walls. I'm not sure how much scripting effort I want to invest here, the results are overall okay and fit a 'goofy' game like DOS wink

3) Hm, characters would be a bit tricky. I can't let NPCs teleport or knock down every character who's possibly, just eventually in the way. Though it's not a viable 'cheese' tactic of players, is it? - Scriptwise, the character would try to move items, and end turn when there isn't left any.

4) I've added ice walls now (just checking for 200000 weight). Characters will attack them until they break.

5) I'll search through the object stats (and the game levels) in the next days, looking for lighter/heavier movable items and unmovable items within the defined weights.

If you consider the final version of the script useful for your mod, feel free to use it smile


My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Dec 2016
Location: United States
member
Offline
member
Joined: Dec 2016
Location: United States
1. I have, I use it in my AI for Phoenix Dive/Tactical Retreat/Misty Step as well as some other scripts. It's very poorly organized, so it's very easy to misuse. It uses the given Float3 both as input location and as its output location, and its "optional" parameter for character/item (the object whose AI bounds are used to calculate what constitutes a valid location) should probably be mandatory.

2. Yeah, probably not a huge worry if they're not killing one another with this behavior.

3. It's not usually something that players can exploit, but it certainly does occur. A great example is the Black Cove's first encounter, there is a small room off to the side where 4 skeletons will attack. If the player stands inside of the door, most of the skeletons will time-out without doing anything. There isn't a great way to solve this, though it would still be an improvement to have creatures immediately pass their turn rather than time-out for 30 seconds.

4. Nice, I'd like to see how you handled it.

5. Good luck :P

Thanks! I would definitely be interested. I have so little time these days though, I'm mostly trying to fix bug reports when I am not committed to other responsibilities.

Last edited by Ameranth; 13/02/17 10:05 PM.
Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
1. Okay, I'll try that. Thanks for the tip!

3. I'll do some testings here.

4. Must test it first, which is more difficult than I thought. They always slip on the ice (they need better boots).

5. Very motivating laugh I just noticed that unmovable items are the reason why characters get stuck in their reaction sometimes. The engine doesn't ignore CharacterMoveItem for unmovable objects and also doesn't interrupt the reaction. The only way to make the character react again is to interrupt the reaction manually or by certain actions (like requesting dialog, or by other reactions). That's not good... It's definitely necessary to exclude all of these items. A check for 'CanBeMoved' or 'CharacterCanMoveItem' (also checking required strength and telekinesis) would be very helpful.
Edit: Maybe I can work with ItemIsMoving. Actually the better idea...
Edit2: Even better: just setting an integer after CharacterMoveItem and checking it on a timer. If it isn't set it will interrupt the reaction.

Last edited by Abraxas*; 14/02/17 02:05 PM.

My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
Currently investigating an issue where characters sometimes stop acting when the path is blocked. It's clear now that it happens in the 'GetClose' reaction, probably due to an unreachable target (the %TempTarget). Now actually this should cause OnMovementFailed and activate the 'MoveItems' reaction. But after some tests it seems there are objects that can block the path but don't cause an interruption!

Test:

1) I placed a double bed in my testlevel, generated Ai grid, and commanded several characters to a position right in the middle of the bed. They didn' move, as expected, and their reaction got interrupted (due to failed movement).

2) I placed a bridge tile (template: 1m_StoneWall_A_Bridge_Big_Floor_A_Item), generated Ai grid, and commanded them to the middle of this object. They DID move as far as they could and stopped when they were close to the object (the min and max range in 'CharacterMoveTo' was set to 0.1, so they were definitely too far away from the targeted position). But there was no interruption!

3) I placed several walls next to each other to get a big wall, generated Ai grid, and commanded them to the middle of this wall construction. They did the same as in 2). Moved as far as they could, stopped, but didn't throw an interruption event!

So after the already mentioned issue with unmovable items causing a character to not react any more (already solved in the script), this is a second source of problems. So I'll try to work with FindValidPosition to prevent characters from choosing targets they can't reach but don't cause an interruption (with the result of a character permanently trying to move to the target without interruption).
Maybe path blocking characters are treated the same as objects in 2) and 3).

I tested this in the editor, so maybe, just maybe, things are different in the game. I hope they aren't. It already took me hours of pointless changes to the script.



My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Dec 2016
Location: United States
member
Offline
member
Joined: Dec 2016
Location: United States
I had noticed that movement failed interrupts didn't always seem to work, but I was too frustrated with it to delve into it. Is it possible that, since the OnMovementFailed routine is part of a behavior, it is also being interrupted?

I think trying to make use of FindValidPosition is a good idea here, as OnMovementFailed seems inconsistent at best.

There is the potential to create your own OnMovementFailed interrupt, though; you could start a timer when ordering a movement behavior that checks the character's Float3 location. If the location is not changing, the character is not moving. The timer is stopped after the move is successful by placing StopTimer below the movement command in the script (or after a reasonable number of executions, to prevent timer buildup).

Last edited by Ameranth; 15/02/17 08:24 PM.
Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
Quote
Is it possible that, since the OnMovementFailed routine is part of a behavior, it is also being interrupted?

Not sure if I understand you right: Currently the missing interruption leads to a loop of the 'GetClose' reaction, it seems, with the character permanently trying to move to a target he cannot reach.
Checking locations could be an option, but I think I'll try to use FindValidPosition first and recalculate a target position if it fails; I can use the _AttemptCounter to stop this behaviour after a few tries.

Though, still assuming this is the actual (and only) problem here. As always, when trying to do something more advanced, issues arise. Quite impressing whenever a mod finally sees the light of the day (not to mention D:OS 1 itself).


My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
Good progress. FindValidPosition helped to fix it (thanks for the INOUT hint, Ameranth, I hadn't noticed that). For the updated script see below.

Changes:

- fixed two problems with characters not reacting in combat (related to unmovable items and unreachable targets)
- characters now attack ice walls if they block path
- characters now attack the closest enemy when their path is blocked and they don't find items (not yet tested)
- objects standing on other objects aren't regarded any more (doesn't work for all types of items other items can stand on)
- re-organized the script to communicate with any combat or peace related script: the 'PathBlockAI' script must be included (#INCLUDE) in 'DefaultCharacter', the INTERRUPT code must be added to every reaction with movement to activate the AI for it (for combat it should be added to reactions of 'DefaultCharacter' and '_SKILLFROMITEM_Base' at least)

Script:

Code
INIT

CHARACTER:__Me

EXTERN FLOAT3:%TargetPos = null
FLOAT3:%TempTarget
INT:%ItemMoveSuccess = 0

BEHAVIOUR

REACTION GetClose, 0
USAGE PEACE	
USAGE COMBAT
VARS
	FLOAT:_distance
	FLOAT:_MyX
	FLOAT:_MyZ
	FLOAT:_TargetX
	FLOAT:_TargetZ
	FLOAT:_AttemptCounter = 0
	FLOAT:_AddX
	FLOAT:_AddZ
	FLOAT:_Divisor
	FLOAT3:_Target
	FLOAT3:_MyPos
	FLOAT3:_TargetPosition
ACTIONS
	Sleep(1)
	Label("Again")
	IF "c1&c2"
		GetPosition(__Me,_MyPos)
		GetVar(_TargetPosition,__Me,"TargetPos")
	THEN
		Set(%TempTarget,_TargetPosition)
	ENDIF
	IF "c1&c2&c3&c4&c5"
		GetX(_MyPos,_MyX)
		GetZ(_MyPos,_MyZ)
		GetX(_TargetPosition,_TargetX)
		GetZ(_TargetPosition,_TargetZ)
		GetDistance(_distance,__Me,_TargetPosition)
	THEN
		Subtract(_TargetX,_MyX)
		Subtract(_TargetZ,_MyZ)
		GetRandom(_Divisor,1.8,1.9,2,2.1,2.2)
		Divide(_TargetX,_Divisor)
		Divide(_TargetZ,_Divisor)
		Add(_MyX,_TargetX)
		Add(_MyZ,_TargetZ)
		SetX(%TempTarget,_MyX)
		SetZ(%TempTarget,_MyZ)
		CharacterLookAt(%TempTarget)
		IF "c1"
			IsGreaterThen(_AttemptCounter,4)
		THEN
			Set(_AttemptCounter,0)
			SetPriority("MoveItems",0)
			IF "c1"
				IsInCombat(__Me)
			THEN
				CharacterEndTurn()
			ELSE
				DelayReaction("GetClose",10)
			ENDIF			
			SetPriority("GetClose",0)
		ELIF "c1"
			IsLessThen(_distance,4)
		THEN	
			CharacterLookAt(_TargetPosition)
			IF "c1"
				IsInCombat(__Me)
			THEN
				CharacterMoveTo(_TargetPosition,1,0,0,0.2,1)
			ELSE
				CharacterMoveTo(_TargetPosition,0,0,0,0.2,1)
			ENDIF
			SetPriority("GetClose",0)
		ELIF "!c1"
			IsLessThen(_distance,4)
		THEN			
			IF "!c1"
				FindValidPosition(%TempTarget,2.5,__Me) 
			THEN	
				Interrupt("GetClose")
			ENDIF
			IF "c1"
				IsInCombat(__Me)
			THEN
				CharacterMoveTo(%TempTarget,1,0,0,0.2,1.4)
			ELSE 
				CharacterMoveTo(%TempTarget,0,0,0,0.2,1.4) 
			ENDIF
			SetPriority("MoveItems",0)
		ELSE
			Set(_AttemptCounter,0)
			SetPriority("MoveItems",0)
			SetPriority("GetClose",0)
		ENDIF
	ENDIF
INTERRUPT
	Reset()
	ON
		OnMovementFailed(_)
		OnManualInterrupt("GetClose")
	ACTIONS
		Add(_AttemptCounter,1)
		SetPriority("MoveItems",3000)
		SetPriority("GetClose",0) 
		
REACTION MoveItems, 0
USAGE PEACE
USAGE COMBAT
VARS
	ITEM:_Item
	ITEM:_Item2
	ITEM:_IgnoreItem1
	ITEM:_IgnoreItem2
	ITEM:_IgnoreItem3
	FLOAT:_Weight
	FLOAT:_X
	FLOAT:_Z
	FLOAT:_MyPosX
	FLOAT:_MyPosZ
	FLOAT3:_MyPos
	CHARACTER:_Enemy
CHECK "(((c1&c2&c3&c4&((!c5&c6)|c7))|(c8&c9&c10&((!c11&c12)|c13)))&!c14&!(c15&c16))|(c17&c18)"
	ItemGet(_Item,__Me,6,Random,Distance,"")
	IsFacing(__Me,_Item)
	CanSee(__Me,_Item)
	ItemGetStat(_Weight,_Item,Weight)
	IsLessThen(_Weight,1000)
	IsLessThen(_Weight,40001)
	IsEqual(_Weight,200000) //Ice_Crystal
	ItemGet(_Item,%TempTarget,6,Random,Distance,"")
	IsFacing(__Me,_Item)
	ItemGetStat(_Weight,_Item,Weight)
	IsLessThen(_Weight,1000)
	IsLessThen(_Weight,40001)
	IsEqual(_Weight,200000) //Ice_Crystal
	ItemIsDestroyed(_Item)
	ItemGet(_Item2,_Item,2,Random,Distance,"")
	IsObjectOnObject(_Item,_Item2)
	IsInCombat(__Me)
	CharacterGet(_Enemy,__Me,5,Lowest,Distance,Enemy)
ACTIONS
	IF "!c1"
		IsEqual(_Enemy,null)
	THEN
		CharacterMoveTo(_Enemy,1)
		CharacterAttack(_Enemy)
		Goto("End")
	ENDIF
	IF "c1"
		IsInCombat(__Me)
	THEN
		CharacterMoveTo(_Item,1)
	ELSE
		CharacterMoveTo(_Item)
	ENDIF
	IF "c1"
		IsEqual(_Weight,200000) //Ice_Crystal
	THEN
		WHILE "!c1"
			ItemIsDestroyed(_Item)
		DO
			CharacterAttack(_Item)
		ENDWHILE
	ELSE
		GetPosition(__Me,_MyPos)
		IF "!c1&c2"
			ItemIsDestroyed(_Item)
			FindValidPosition(_MyPos,5,_Item)
		THEN
			StartTimer("CheckMoveItemSuccess",3,0)
			CharacterMoveItem(_Item,_MyPos)
			Set(%ItemMoveSuccess,1)			
		ENDIF
	ENDIF
	Label("End")
	Set(_Enemy,null)
	SetPriority("MoveItems",0)
INTERRUPT
	Reset()
	Set(_Enemy,null)
	SetPriority("MoveItems",0)
	
EVENTS

EVENT CheckItemMoveSuccess
ON
	OnTimer("CheckMoveItemSuccess")
ACTIONS
	IF "c1"
		IsEqual(%ItemMoveSuccess,1)
	THEN
		Set(%ItemMoveSuccess,0)
	ELSE
		Interrupt("MoveItems")
	ENDIF
	
EVENT GetCloseActivation
ON
	OnCharacterEvent(__Me,"ActivateGetClose")
ACTIONS
	SetPriority("GetClose",2000)

INTERRUPT code:

Code

 //to be added to every reaction with movement

VARS
	FLOAT3:_Pos

INTERRUPT
//Reset() //not necessary for functionality, but for most reactions recommended
ON
	OnMovementFailed(_Pos)
ACTIONS
	SetVar(__Me,"TargetPos",_Pos)
	CharacterEvent(__Me,"ActivateGetClose")


Some notes:

- sometimes characters won't act for a few seconds if they fail to find a solution; though they'll skip turn after a while (combat) or delay the reaction (peace)

- characters can't find path blocking items if they aren't facing the position (the closer to the target the more likely this happens); increasing the angle too much would make the script more unprecise

- it's still unclear under which conditions a failing movement doesn't cause an interruption; as long as this happens in the 'GetClose' reaction, it will just lead to a character skipping turn if he doesn't find another solution (or delaying the reaction); if it happens in any other peace or combat reaction this will lead to a character not acting at all (peace) or for a longer while (combat, assumed that the 'DelayReaction' call in the reactions of 'DefaultCharacter' or '_SKILLFROMITEM_Base' was removed - I always do that, since DelayReaction seems to be broken in combat, not counting seconds but eternities)

- characters still aren't super-intelligent, but they're definitely not helpless any more


My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Dec 2016
Location: United States
member
Offline
member
Joined: Dec 2016
Location: United States
I'm glad you're making such progress! This is very promising stuff indeed.

- Would it makes sense to have characters turn to face the target before ever attempting a move? This would (usually) ensure facing is correct.

- I didn't know that DelayReaction didn't consider seconds while in combat... I'll have to remember this.

- My only criticism at a glance is the very large complex conditional for MoveItems' Check critera (performance concern), but I think this behavior never maintains a priority for very long, correct?

Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
1) That's already the case. But the line of sight between the character and the target (the shortest path) can be completely different from the actual path, mostly when buildings come into play (with a blocked door). But it doesn't happen very often, and is not easy to exploit.

3) I also feel a bit uncomfortable about this, but this reaction is only active on demand for a few seconds and, of course, only for one single character. Though it would be useful to measure the influence of this reaction on performance. Epic Encounters is quite scripting-heavy, so it's a reasonable concern.


My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
Update:

- fixed an issue where characters repeatedly moved items to the exact same position when their own position didn't change (FindValidPosition always gives the same output if nothing changed)
- improved decisionmaking in finding items to move (the closest item will be chosen with a 40% chance, since it's often the best solution)
- removed IsObjectOnObject check

Script:

Code
INIT

CHARACTER:__Me

EXTERN FLOAT3:%TargetPos = null
FLOAT3:%TempTarget
INT:%ItemMoveSuccess = 0

BEHAVIOUR

REACTION GetClose, 0
USAGE PEACE	
USAGE COMBAT
VARS
	FLOAT:_distance
	FLOAT:_MyX
	FLOAT:_MyZ
	FLOAT:_TargetX
	FLOAT:_TargetZ
	FLOAT:_AttemptCounter = 0
	FLOAT:_AddX
	FLOAT:_AddZ
	FLOAT:_Divisor
	FLOAT3:_Target
	FLOAT3:_MyPos
	FLOAT3:_TargetPosition
ACTIONS
	IF "c1&c2"
		GetPosition(__Me,_MyPos)
		GetVar(_TargetPosition,__Me,"TargetPos")
	THEN
		Set(%TempTarget,_TargetPosition)
	ENDIF
	IF "c1&c2&c3&c4&c5"
		GetX(_MyPos,_MyX)
		GetZ(_MyPos,_MyZ)
		GetX(_TargetPosition,_TargetX)
		GetZ(_TargetPosition,_TargetZ)
		GetDistance(_distance,__Me,_TargetPosition)
	THEN
		Subtract(_TargetX,_MyX)
		Subtract(_TargetZ,_MyZ)
		GetRandom(_Divisor,1.8,1.9,2,2.1,2.2)
		Divide(_TargetX,_Divisor)
		Divide(_TargetZ,_Divisor)
		Add(_MyX,_TargetX)
		Add(_MyZ,_TargetZ)
		SetX(%TempTarget,_MyX)
		SetZ(%TempTarget,_MyZ)
		CharacterLookAt(%TempTarget)
		IF "c1"
			IsGreaterThen(_AttemptCounter,4)
		THEN
			Set(_AttemptCounter,0)
			SetPriority("MoveItems",0)
			IF "c1"
				IsInCombat(__Me)
			THEN
				CharacterEndTurn()
			ELSE
				DelayReaction("GetClose",5)
			ENDIF			
			SetPriority("GetClose",0)
		ELIF "c1"
			IsLessThen(_distance,4)
		THEN	
			CharacterLookAt(_TargetPosition)
			IF "c1"
				IsInCombat(__Me)
			THEN
				CharacterMoveTo(_TargetPosition,1,0,0,0.2,1)
			ELSE
				CharacterMoveTo(_TargetPosition,0,0,0,0.2,1)
			ENDIF
			SetPriority("GetClose",0)
		ELIF "!c1"
			IsLessThen(_distance,4)
		THEN			
			IF "!c1"
				FindValidPosition(%TempTarget,4,__Me) 
			THEN										
				Interrupt("GetClose")
			ENDIF
			IF "c1"
				IsInCombat(__Me)
			THEN
				CharacterMoveTo(%TempTarget,1,0,0,0.2,1.4)
			ELSE 
				CharacterMoveTo(%TempTarget,0,0,0,0.2,1.4) 
			ENDIF
			SetPriority("MoveItems",0)
		ELSE
			Set(_AttemptCounter,0)
			SetPriority("MoveItems",0)
			SetPriority("GetClose",0)
		ENDIF
	ENDIF
INTERRUPT
	Reset()
	ON
		OnMovementFailed(_)
		OnManualInterrupt("GetClose")
	ACTIONS
		Add(_AttemptCounter,1)
		SetPriority("MoveItems",3000)
		SetPriority("GetClose",0) 
		
REACTION MoveItems, 0
USAGE PEACE
USAGE COMBAT
VARS
	ITEM:_Item
	ITEM:_Item2
	ITEM:_IgnoreItem1
	ITEM:_IgnoreItem2
	ITEM:_IgnoreItem3
	FLOAT:_Weight
	FLOAT:_X
	FLOAT:_Z
	FLOAT:_MyPosX
	FLOAT:_MyPosZ
	FLOAT3:_MyPos
	CHARACTER:_Enemy
CHECK "(((((c1&c2)|(c3&c4))&c5&c6&((!c7&c8)|c9))|(c10&c11&c12&((!c13&c14)|c15)))&!c16)|(c17&c18)"
	IsRandom(0.4)
	ItemGet(_Item,__Me,6,Lowest,Distance,"")
	ItemGet(_Item,__Me,6,Random,Distance,"")
	CanSee(__Me,_Item)
	IsFacing(__Me,_Item)
	ItemGetStat(_Weight,_Item,Weight)
	IsLessThen(_Weight,1000)
	IsLessThen(_Weight,40001)
	IsEqual(_Weight,200000) //Ice_Crystal
	ItemGet(_Item,%TempTarget,6,Random,Distance,"")
	IsFacing(__Me,_Item)
	ItemGetStat(_Weight,_Item,Weight)
	IsLessThen(_Weight,1000)
	IsLessThen(_Weight,40001)
	IsEqual(_Weight,200000) //Ice_Crystal
	ItemIsDestroyed(_Item)
	IsInCombat(__Me)
	CharacterGet(_Enemy,__Me,5,Lowest,Distance,Enemy)
ACTIONS
	IF "!c1"
		IsEqual(_Enemy,null)
	THEN
		CharacterMoveTo(_Enemy,1)
		CharacterAttack(_Enemy)
		Goto("End")
	ENDIF
	IF "c1"
		IsInCombat(__Me)
	THEN
		CharacterMoveTo(_Item,1)
	ELSE
		CharacterMoveTo(_Item)
	ENDIF
	IF "c1"
		IsEqual(_Weight,200000) //Ice_Crystal
	THEN
		WHILE "!c1"
			ItemIsDestroyed(_Item)
		DO
			CharacterAttack(_Item)
		ENDWHILE
	ELSE
		GetPosition(__Me,_MyPos)
		GetRandom(_X,-3,-2,2,3)
		GetRandom(_Z,-3,-2,2,3)
		IF "!c1&c2&c3"
			ItemIsDestroyed(_Item)
			GetX(_MyPos,_MyPosX)
			GetZ(_MyPos,_MyPosZ)
		THEN
			Subtract(_MyPosX,_X)
			Subtract(_MyPosZ,_Z)
			SetX(_MyPos,_MyPosX)
			SetZ(_MyPos,_MyPosZ)
			IF "c1"
				FindValidPosition(_MyPos,4,_Item)
			THEN
				StartTimer("CheckMoveItemSuccess",3,0)
				CharacterMoveItem(_Item,_MyPos)
				Set(%ItemMoveSuccess,1)	
			ENDIF
		ENDIF
	ENDIF
	Label("End")
	Set(_Enemy,null)
	SetPriority("MoveItems",0)
INTERRUPT
	Reset()
	Set(_Enemy,null)
	SetPriority("MoveItems",0)
	
EVENTS

EVENT CheckItemMoveSuccess
ON
	OnTimer("CheckMoveItemSuccess")
ACTIONS
	IF "c1"
		IsEqual(%ItemMoveSuccess,1)
	THEN
		Set(%ItemMoveSuccess,0)
	ELSE		
		Interrupt("MoveItems")
	ENDIF
	
EVENT GetCloseActivation
ON
	OnCharacterEvent(__Me,"ActivateGetClose")
ACTIONS
	SetPriority("GetClose",2000)

INTERRUPT code:

Code
 //to be added to every reaction with movement

VARS
	FLOAT3:_Pos

INTERRUPT
//Reset() //not necessary for functionality, but for most reactions recommended
ON
	OnMovementFailed(_Pos)
ACTIONS
	SetVar(__Me,"TargetPos",_Pos)
	CharacterEvent(__Me,"ActivateGetClose")


Last edited by Abraxas*; 22/02/17 10:39 AM. Reason: Little correction in FindValidPosition; removed Sleep(1)

My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Dec 2016
Location: United States
member
Offline
member
Joined: Dec 2016
Location: United States
Very nice progress, I'll probably add this into EE for internal testing after I send out the next patch as well (assuming you're still okay with that).

Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
I'm completely okay with that (I'm not even sure if I'd had started another attempt on this without your mod, at least not immediately). A test would be very helpful, especially in regard to finetuning (wherever possible).


My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
I rechecked the behaviour of DelayReaction in combat: I'd highly recommend to disable it in the relevant reactions (it's always used OnMovementFailed in several reactions of combat scripts). It always delays the reaction for the whole turn (at least), regardless of the entered seconds. But 3 'seconds' or 200 'seconds' seems to make a difference, though I don't know what or when the engine counts here (during WAITING?). It doesn't count turns instead of seconds, that's for sure.
For my scripts to work properly, the reactions of combat scripts must not be delayed for the whole turn but stay active. There's no fallback then, but my script contains a rule for ending the turn when failed movement interrupted those reactions (--> four attempts to find a solution).


My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Dec 2016
Location: United States
member
Offline
member
Joined: Dec 2016
Location: United States
Elric will be testing with your scripting once I have this next patch uploaded, then smile The base game's AI has proven to cause enemies' turns to time-out relatively often, so this will be wonderful in addressing even just that.

Good to know about DelayReaction; I didn't use it too much because it always felt broken, but I'll keep in mind that it really is broken moving forward.

Joined: Sep 2015
A
addict
OP Offline
addict
A
Joined: Sep 2015
Quote

The base game's AI has proven to cause enemies' turns to time-out relatively often, so this will be wonderful in addressing even just that.

If it's related to failing movement, it will probably help.

Quote

Elric will be testing with your scripting once I have this next patch uploaded, then smile

All right. He'll probably choose other test situations than I did. Will human or AI succeed?

I've made one little change to the script above. Just an adjustment in FindValidPosition for the temporary target (from 2.5 range to 4).
I'm uncertain if I should set the priorities of 'GetClose' and 'MoveItems' to 0 OnTurn. Not sure if it's needed to prevent characters from continuing actions they couldn't finish on their last turn (due to missing action points) although the situation was already solved by another character. And it would ensure that there's never an issue with unintentionally remaining priorities. If Elric observes behaviour of this kind, it should be added.





My mods for DOS 1 EE: FasterAnimations - QuietDay - Samaritan
Joined: Dec 2016
Location: United States
member
Offline
member
Joined: Dec 2016
Location: United States
Hey Abraxas, since I'm lazy and a noob (been busy lately), would you mind uploading the relevant scripts from Epic Encounters with your changes so that I can just paste them in for Elric and have him start testing?

<3 u

Last edited by Ameranth; 22/02/17 06:10 AM.
Page 1 of 3 1 2 3

Link Copied to Clipboard
Powered by UBB.threads™ PHP Forum Software 7.7.5