Coding the AI to throw items!!!

Feb 5, 2011, 4:03 am
#1
Joined: Sep 8, 2010
Occupation: Petty Functionary
Location: Drinking pea soup in the world map
Interests: Mangoes
Posts: 1,192
I have developed a stable algorithm principally for use in LIVAN, but could concievably be used in IVAN, which enables the AI to throw certain items at hostile creatures in the game. It is the result of several hours of sourcediving and general fooling around with the code.

The algorithm itself is straightforward:

- The thrower has to be humanoid, among other things.
- Has to have at least one arm
- Has to be a ranged attacker (this is new)
- First he checks the area for hostiles he can see:
He looks in all eight directions for a suitable throwing direction, free from friendlies and a direction that contains the nearest hostile. The status of the directions are stored in a buffer; they will either be ZERO, SUCCESS or BLOCKED.
- Once he has checked out the whole area, and has a SUCCESS direction, he checks his inventory for a suitable throwing weapon. The throwing weapons have been implemented as AXE and DAGGER in this case.
- If he has something suitable to throw, he will throw that item in the direction of the hostile he picked up on his radar

Now for practical purposes, the thrower has been implemented as a novel dwarf type character, "axethrowerdwarf", resembling a kamakaze dwarf, but without the backpack.
There is also a modification to the "CheckForUsefulItemsOnGround", where the dwarf will pick up suitable objects he can throw, and hoards them in his inventory for later use. That way he can re-stock his ammo.

The axethrower dwarf may seem a little underpowered, however, this algorithm is designed to provide scope for anyone who wants to create the abominable "Grenadier Dwarves" that have been mentioned somewhere...
That would require a simple change to some of the gas-grenade item-properties in the script, and perhaps a change to the Grenadier Dwarf's initial equipment :E

I hope I have catalogued everything that is required, let me know if it doesn't compile. Code additions are in bold-faced type. Here it is:

[color=#9acd32]// To make the AI throw items, add the following lines of code:

// In human.h:[/color]

CHARACTER(humanoid, character)
{
	public:
	...
	[b]virtual truth CheckThrowItemOpportunity();[/b]
	...
}

[b]CHARACTER(axethrowerdwarf, humanoid)
{
 public:
  virtual void GetAICommand();
  virtual void CreateInitialEquipment(int);
 protected:
  virtual int GetTorsoMainColor() const;
  virtual int GetGauntletColor() const;
  virtual int GetLegMainColor() const;
  virtual v2 GetDrawDisplacement(int) const;
  virtual int GetWSkillHits() const { return 10000; }
  virtual truth IsElite() const { return false; }
};[/b]

[color=#9acd32]// In human.cpp:[/color]
[b]
void axethrowerdwarf::CreateInitialEquipment(int SpecialFlags)
{
  SetRightWielded(meleeweapon::Spawn(AXE, SpecialFlags));
	SetLeftWielded(meleeweapon::Spawn(AXE, SpecialFlags));
	for(int k = 0; k < 6; k++)
	{
		GetStack()->AddItem(meleeweapon::Spawn(AXE));
	}
	GetStack()->AddItem(lantern::Spawn());
  GetCWeaponSkill(AXES)->AddHit(GetWSkillHits());
  GetCurrentRightSWeaponSkill()->AddHit(GetWSkillHits());
}

void axethrowerdwarf::GetAICommand()
{
	if(CheckThrowItemOpportunity())
		return;
		
  character::GetAICommand();
}

v2 axethrowerdwarf::GetDrawDisplacement(int j) const
{
  static v2 DrawDisplacement[] = { v2(0, 0), v2(0, 1), v2(0, -1), v2(0, -1), v2(0, -1), v2(0, 0), v2(0, 0) };
  return DrawDisplacement[j];
}

col16 axethrowerdwarf::GetTorsoMainColor() const
{
  return GetMasterGod()->GetColor();
}

col16 axethrowerdwarf::GetGauntletColor() const
{
  return GetMasterGod()->GetColor();
}

col16 axethrowerdwarf::GetLegMainColor() const
{
  return GetMasterGod()->GetColor();
}

truth humanoid::CheckThrowItemOpportunity()
{
	if(!HasAUsableArm())
		return false;
	else
		return character::CheckThrowItemOpportunity();
}

[/b]
[color=#9acd32]// In char.dat:[/color]

character
{
	...
	[b]IsRangedAttacker = false;[/b]
}

[b]axethrowerdwarf
{
  DefaultArmStrength = 20;
  DefaultLegStrength = 20;
  DefaultDexterity = 20;
  DefaultAgility = 10;
  DefaultEndurance = 12;
  DefaultPerception = 18;
  DefaultIntelligence = 10;
  DefaultWisdom = 5;
  DefaultCharisma = 10;
  DefaultMana = 10;
  HeadBitmapPos = 112, 160;
  TorsoBitmapPos = 48, 16;
  ArmBitmapPos = 80, 0;
  LegBitmapPos = 0, 208;
  HairColor = rgb16(144, 72, 0);
  BeltColor = rgb16(72, 56, 16);
  TotalVolume = 50000;
  TotalSize = 100;
  CanRead = true;
  NameSingular = "axethrower dwarf";
  NamePlural = "axethrower dwarves";
  CanBeGenerated = true;
  CreateDivineConfigurations = true;
  IsAbstract = true;
  /* Equipment initialization is overridden */
  PanicLevel = 1;
  Inventory == IRON meleeweapon(AXE) {Times = 6;}
  FleshMaterial = DWARF_FLESH;
  DeathMessage = "@Dd dies in agony.";
  IgnoreDanger = true;
  Frequency = 300;
  HostileReplies == "\\"In the name of @Gd I will slash you to pieces!\\"";
  AttachedGod = NONE;
  UndeadVersions = false;
  WieldedPosition = -1, -2;
  FriendlyReplies =
  {
    4,
    "\\"Would you like me to teach you the best way to slice tomatoes?\\"",
    "@Dd shouts: \\"Death to disbelievers!\\"",
    "@Dd praises @Gd with numerous hymns. @Pp is obviously a very devoted follower.",
    "\\"One day, Holy War will break out, hopefully I will earn my backpack by then!\\"";
  }
	IsRangedAttacker = true;
	KnownCWeaponSkills == AXES;
	CWeaponSkillHits == 100;
  RightSWeaponSkillHits = 50;
	UsesLongArticle = true;
}
[/b]
[color=#9acd32]// In database.cpp:[/color]

template<> void databasecreator<character>::CreateDataBaseMemberMap()
{
	...
	[b]ADD_MEMBER(IsRangedAttacker);[/b]
}

template<> void databasecreator<item>::CreateDataBaseMemberMap()
{
	...
	[b]ADD_MEMBER(IsThrowingWeapon);[/b]
}

[color=#9acd32]// In item.dat:[/color]

item
{
	...
	[b]IsThrowingWeapon = false;[/b]
}

	Config AXE;
  {
    ...
		[b]IsThrowingWeapon = true;[/b]
  }
	
	Config DAGGER; // if you like throwing daggers :E
  {
    ...
		[b]IsThrowingWeapon = true;[/b]
  }

[color=#9acd32]// In item.h:[/color]

struct itemdatabase : public databasebase
{
	...
	[b]truth IsThrowingWeapon;[/b]
};

class item : public object
{
	...
	[b]DATA_BASE_TRUTH(IsThrowingWeapon);[/b]
	...
}

[color=#9acd32]// In char.h:[/color]

struct characterdatabase : public databasebase
{
	...
	[b]truth IsRangedAttacker;[/b]
};

class character : public entity, public id
{
 public:
	...
	[b]truth TryToAddToInventory(item*);[/b]
	...
	[b]DATA_BASE_TRUTH(IsRangedAttacker);[/b]
	...
#endif
	...
	[b]truth CheckThrowItemOpportunity();[/b]
	...
}

[color=#9acd32]// In char.cpp[/color]

truth character::CheckForUsefulItemsOnGround(truth CheckFood)
{
  ...

  for(uint c = 0; c < ItemVector.size(); ++c)
    if(ItemVector[c]->CanBeSeenBy(this) && ItemVector[c]->IsPickable(this))
    {
      if(!(CommandFlags & DONT_CHANGE_EQUIPMENT)
				&& TryToEquip(ItemVector[c]))
				return true;

      if(CheckFood && UsesNutrition() && !CheckIfSatiated()
				&& TryToConsume(ItemVector[c]))
				return true;
[b]
			if( (ItemVector[c])->IsThrowingWeapon() && IsRangedAttacker() 
				&& TryToAddToInventory(ItemVector[c]))
				return true; [/b]

    }
  return false;
}
[b]
truth character::TryToAddToInventory(item* Item)
{
  if(!(GetBurdenState() > STRESSED)
     || !CanUseEquipment()
     || Item->GetSquaresUnder() != 1)
    return false;

  room* Room = GetRoom();

	if(!Room || Room->PickupItem(this, Item, 1))
	{
		if(CanBeSeenByPlayer())
			ADD_MESSAGE("%s picks up %s from the ground.", CHAR_NAME(DEFINITE), Item->CHAR_NAME(INDEFINITE));

		Item->MoveTo(GetStack());
		DexterityAction(5);
		return true;
	}

  return false;
}
[/b]

[color=#9acd32]//finally, the algorithm itself[/color]
[b]
truth character::CheckThrowItemOpportunity()
{
  if(!IsRangedAttacker() || !CanThrow() || !IsHumanoid() || !IsSmall() || !IsEnabled()) // total gum
    return false;
	
	// Steps:
	// (1) - Acquire target as nearest enemy
	// (2) - Check that this enemy is in range, and is in appropriate direction
	//			 No friendly fire!
	// (3) - check inventory for throwing weapon, select this weapon
	// (4) - throw item in direction where the enemy is
	
	//Check the visible area for hostiles
	v2 Pos = GetPos();
	v2 TestPos;
	int RangeMax = GetLOSRange();
	int CandidateDirections[7] = {0, 0, 0, 0, 0, 0, 0};
	int HostileFound = 0;
	
	for(int r = 1; r <= RangeMax; ++r) 
	{
		for(int dir = 0; dir < 8; ++dir) 
		{
			switch(dir)
			{
				case 0: 
				TestPos = v2(Pos.X - r, Pos.Y - r);
				break;
				case 1: 
				TestPos = v2(Pos.X, Pos.Y - r);
				break;
				case 2: 
				TestPos = v2(Pos.X + r, Pos.Y - r);
				break;
				case 3: 
				TestPos = v2(Pos.X - r, Pos.Y);
				break;
				case 4: 
				TestPos = v2(Pos.X + r, Pos.Y);
				break;
				case 5: 
				TestPos = v2(Pos.X - r, Pos.Y + r);
				break;
				case 6: 
				TestPos = v2(Pos.X, Pos.Y + r);
				break;
				case 7: 
				TestPos = v2(Pos.X + r, Pos.Y + r);
				break;
			}
			
			level* Level = GetLevel();
			
			if(Level->IsValidPos(TestPos))
			{
				square* TestSquare = GetNearSquare(TestPos);
				character* Dude = TestSquare->GetCharacter();
				
				if((Dude && Dude->IsEnabled() && (GetRelation(Dude) != HOSTILE)
								&& Dude->CanBeSeenBy(this, false, true))) 
				{
					CandidateDirections[dir] = BLOCKED;
				}
				
				if(Dude && Dude->IsEnabled() && (GetRelation(Dude) == HOSTILE)
								&& Dude->CanBeSeenBy(this, false, true)
								&& (CandidateDirections[dir] != BLOCKED))
				{
					//then load this candidate position direction into the vector of possible throw directions
					CandidateDirections[dir] = SUCCESS;
					HostileFound = 1;
				}
			}
		}
	}
	
	int ThrowDirection = 0;
	int TargetFound = 0;
	
	if(HostileFound)
	{
		for(int dir = 0; dir < 8; ++dir)
		{
			if( (CandidateDirections[dir] == SUCCESS) && !TargetFound)
			{
				ThrowDirection = dir;
				TargetFound = 1;
			}
		}
		if(!TargetFound)
		{
			return false;
		}
	}
	else
	return false;
	
	//check inventory for throwing weapon
	itemvector ItemVector;
  GetStack()->FillItemVector(ItemVector);
	item* ToBeThrown = 0;

  for(uint c = 0; c < ItemVector.size(); ++c)
    if(ItemVector[c]->IsThrowingWeapon())
		{
      ToBeThrown = ItemVector[c];
			break;
		}
	
	if(!ToBeThrown)
		return false;
	
	if(CanBeSeenByPlayer())
    ADD_MESSAGE("%s throws %s.", CHAR_NAME(DEFINITE), ToBeThrown->CHAR_NAME(INDEFINITE));
	ThrowItem(ThrowDirection, ToBeThrown);
  EditExperience(ARM_STRENGTH, 75, 1 << 8);
  EditExperience(DEXTERITY, 75, 1 << 8);
  EditExperience(PERCEPTION, 75, 1 << 8);
  EditNP(-50);
  DexterityAction(5);
	TerminateGoingTo();
	return true;
	
}
[/b]
[color=#9acd32]// the end[/color]

Batman? wrote
its been so long since i had gotten that far i didnt think it through. arrrr!!!!!!
Feb 5, 2011, 4:43 am
#2
Joined: Apr 1, 2008
Occupation: Man-At-Arms
Location: Podolia
Interests: Swingin' mah blade
Posts: 78
You are a true HERO dear Sir, this piece of code does makes IVAN more complete.
Feb 5, 2011, 4:50 am
#3
Joined: Dec 11, 2008
Posts: 1,770
We're most certainly lucky to have you Warheck.
If it works in LIVAN, it should theoretically work in IVAN CVS.
Also in relation to grenadier dwarves, you had best make them appear only around the time kamikaze dwarves or we'll be having mass amounts of "____ dissolved by mustard gas in Underwater Tunnel level 1"
System would indicate in graphic if person is mounted on horse or not.
Same system also show if person mounted on boar, elephant, polar bear etc.
Or if person mounted on ass.
Ivan find mounting on ass funny.
Feb 5, 2011, 1:57 pm
#4
Joined: Dec 2, 2007
Location: New Attnam
Interests: bananas
Posts: 2,261
This sounds awesome!! Can't wait to try it. Dwarves throwing gas grenades, that's a scary thought.
Feb 5, 2011, 9:33 pm
#5
Joined: Dec 3, 2007
Occupation: Chaos Weaver
Location: Standing between all life and death
Posts: 2,863
Oh wow, this sounds awesome. You've even made it noob-friendly for those who can't code! A pineapple for you, sir!

Uchuudonge wrote
creating stable chaos
making patterns where there should be none
sewing order into the chaos
you spit in the face of random numbers, of chaos
Feb 5, 2011, 11:46 pm
#6
kobold lord


Joined: Dec 15, 2010
Interests: mutant ass
Posts: 100
Hey, don't forget potions of acid! Those could be fun.
Feb 6, 2011, 1:04 am
#7
Joined: Sep 8, 2010
Occupation: Petty Functionary
Location: Drinking pea soup in the world map
Interests: Mangoes
Posts: 1,192
Thank you all for your adulations. Yep. Potions, grenades, bones of Ullr. Heck, even AI instigated wand-zapping could be facilitated.
I attach here some photos of a dude being attacked by the axe-dwarf, and also of the dwarf picking up an axe from the ground. While the code probably isn't as streamlined as it could potentially be, it seems to work.
Batman? wrote
its been so long since i had gotten that far i didnt think it through. arrrr!!!!!!
Feb 6, 2011, 1:30 am
#8
kobold lord


Joined: Dec 15, 2010
Interests: mutant ass
Posts: 100
An axedwarf "of dulcis" seems kind of weird...
Feb 6, 2011, 7:50 am
#9
Joined: Jan 11, 2008
Posts: 1,019
Is it any more sensible than a kamikaze dwarf of Silva?

Warheck: Good show, man. The idea of a grenade/poison/acid-chucker is a frightening thought, but well within the spirit of the game, as is the chucker of axes and daggers. In fact a dagger-thrower would be good to have; I don't usually use my run button a whole lot outside of maybe trying to train on the world map (when I do get around to playing anyway).
Feb 6, 2011, 9:48 am
#10
Joined: Dec 17, 2007
Occupation: Taking Names, Formerly Kicking Ass
Location: New Jersey
Posts: 970
does it keep the monster from throwing its equippend weapon? because it could lead to a situation where a lot of enemies you would otherwise have a hard time with fighting your barehanded.

rondol throws his spear, it misses, rondol realizes he is fucked
Booooooooooo!
Feb 6, 2011, 11:53 am
#11
Joined: Dec 4, 2007
Occupation: Perfect Soldier
Location: Astragius Galaxy
Interests: Fiana, Peace, Melons
Posts: 1,055
Batman? wrote
does it keep the monster from throwing its equippend weapon? because it could lead to a situation where a lot of enemies you would otherwise have a hard time with fighting your barehanded.

rondol throws his spear, it misses, rondol realizes he is fucked


Quote
- Has to be a ranged attacker (this is new)

I don't think that's a problem.

Aside from that, great googly moogly, Warheck, well done!
Proudly bringing disaster and mental scarring to Attnam since '05!

"You have a rather pleasant chat about finite superarmpits with Sanae the shrine maiden."

You hear distant shuffling.

The Enner Beast tells you to COOL IT!!
Feb 6, 2011, 4:04 pm
#12
Joined: Sep 8, 2010
Occupation: Petty Functionary
Location: Drinking pea soup in the world map
Interests: Mangoes
Posts: 1,192
Batman? wrote
does it keep the monster from throwing its equippend weapon? because it could lead to a situation where a lot of enemies you would otherwise have a hard time with fighting your barehanded.

rondol throws his spear, it misses, rondol realizes he is fucked

A good question. The thrower will only search his inventory for things that can be thrown, according to the GetStack() function. He will not throw his equipped weapons.
Furthermore, the spear would have to be IsThrowingWeapon = true; in the config in item.dat.
Heinously, the axedwarf will actually dual wield his two equipped axes to the bitter end.
Batman? wrote
its been so long since i had gotten that far i didnt think it through. arrrr!!!!!!
Feb 8, 2011, 12:21 am
#13
Joined: Dec 11, 2008
Posts: 1,770
I think someone needs to get working on a dwarven sub-dungeon (e.g. similar in length to the dragon cave, but dwarf themed)

Also I had this idea for "ninja smoke", which when it is released from dwarven gas grenades, creates a small puff of smoke and teleports the user away. I think I even did a bit of code (probably script actually) for it, i'll have to have a search.
With this and Warheck's work we could possibly make horribly annoying ninja dwarves.
System would indicate in graphic if person is mounted on horse or not.
Same system also show if person mounted on boar, elephant, polar bear etc.
Or if person mounted on ass.
Ivan find mounting on ass funny.
Feb 8, 2011, 6:20 am
#14
Joined: Sep 8, 2010
Occupation: Petty Functionary
Location: Drinking pea soup in the world map
Interests: Mangoes
Posts: 1,192
They could throw shuriken. They could be ironic ninja dwarves, without all the stealth and subtlety.
I think a smoke cloud that teleports characters that step into it could be interesting.
Or a gas cloud that teleports itself around the level. Can smoke be teleported?
Batman? wrote
its been so long since i had gotten that far i didnt think it through. arrrr!!!!!!
Feb 8, 2011, 3:00 pm
#15
Joined: Dec 11, 2008
Posts: 1,770
Ironic ninja dwarves sounds exactly like something IVAN would have!
And I don't believe smoke can be teleported - but then again I've never tried it.
System would indicate in graphic if person is mounted on horse or not.
Same system also show if person mounted on boar, elephant, polar bear etc.
Or if person mounted on ass.
Ivan find mounting on ass funny.
Feb 8, 2011, 7:42 pm
#16
kobold lord


Joined: Dec 15, 2010
Interests: mutant ass
Posts: 100
You could just have the same effect as breaking a wand of teleport+create smoke.
Feb 8, 2011, 10:32 pm
#17
Joined: Dec 11, 2008
Posts: 1,770
IIRC the script I had used the "EFFECT_TELEPORT" (or whatever it is) under "Breathe effect" for the "NINJA_SMOKE" entry.
System would indicate in graphic if person is mounted on horse or not.
Same system also show if person mounted on boar, elephant, polar bear etc.
Or if person mounted on ass.
Ivan find mounting on ass funny.
Feb 15, 2011, 12:23 am
#18
Joined: Dec 4, 2007
Occupation: Computer Artist
Location: That's private. >:U
Interests: Drawing
Posts: 46
*stealth mode deactivated*
Warheck, you dont know me but please have some more adulation: this is awesome, keep it up.
*stealth mode activated*
Feb 16, 2011, 7:31 pm
#19
Joined: Dec 2, 2007
Occupation: Big Daddy
Location: Under a pile of my own offspring
Interests: Caves
Posts: 612
Hmmm. If the dwarves are aligned with gods, perhaps they could start with items aligned with that god in their inventory. E.g. wands to zap, items to throw, minimal chance of nice items. Why not make a new class of Kamikaze dwarf called rookie Kamikaze dwarf that carries a back pack but is afraid to use it and throws things only. Add in Elite Kamikaze dwarf that carries a backpack and will use it when he runs out of things to throw/zap and has a chance of carrying a nice item aligned with his god. Maybe the ultimate is the ninja Kamikaze dwarf that will disappear in a puff of smoke every time you get close enough to melee (like that bastard Izzy) and has all the elite Kamikaze dwarf abilities. You could make his ninja backpack a wand hybrid (probably by modifying wand code) have 5 teleport charges with only a local square effect when zapped (teleports only the wearer) and a major explosion when applied. Could be a formidable foe running around picking up wands and zapping you then teleporting out. You could also give him teleportitis and teleport control so that he could teleport more than just 5 times. Or adapt Blink dog code so that he could teleport at will. Maybe even make the backpacks aligned with the gods so that if you had a ninja backpack of loricatus and he was pissed at you and you tried to use it it would either explode or teleport away a random body part. As in "You zap the ninja backpack of loricatus. Somehow you feel Loricatus is angry. Your groin is teleported away. Suddenly you feel uncertain of your manhood. You die."
Feb 17, 2011, 5:56 am
#20
Joined: Sep 8, 2010
Occupation: Petty Functionary
Location: Drinking pea soup in the world map
Interests: Mangoes
Posts: 1,192
slob wrote
Maybe the ultimate is the ninja Kamikaze dwarf that will disappear in a puff of smoke every time you get close enough to melee (like that bastard Izzy) and has all the elite Kamikaze dwarf abilities.

I like this part. Why not have the ninja kamikaze dwarf disappear with a large explosion i.s.o. smoke, so that he teleports right before the explosion is triggered? He would then survive, and aligned god restocks his backpack right after. Evil eh?

I also like the idea of renaming the axedwarf as a Rookie Kamakaze dwarf who comes equipped with a backpack but refuses to use it, throwing objects instead. It develops that character's element of tragic desperation and it seems to fit better in light of the existing kamakaze dwarf heirarchy.

Aligned backpacks could also have different effects depending on your relation with the gods. If you were in really good standing (champion or 800+ say?) with Atavus, and applied his backpack, he could protect you from the ensuing explosion. On the other hand, if you were in normal or poor standing with Cruentus, and applied his backpack, then you would take damage as well as other bystanders. The size of the explosion could be proportional to the modulus of your relation with that god.
Batman? wrote
its been so long since i had gotten that far i didnt think it through. arrrr!!!!!!
Feb 17, 2011, 5:42 pm
#21
kobold lord


Joined: Dec 15, 2010
Interests: mutant ass
Posts: 100
rookie dwarves should die screaming instead of with a smile on their face.
Feb 19, 2011, 1:35 pm
#22
Joined: Jan 25, 2008
Occupation: Turns out I can't have a cool avatar, I'll just put crap in it.
Location: 48kg / 105 lbs
Interests: My name is Failbert
Posts: 697
Screaming "War is hell!" and other quotes.
Well I assume that a bolt has a given, numeric, amouunt of perwer. (assi in enegri)... unless it's LAERSER SKISKLS POEWN PEWN!
Feb 28, 2011, 4:22 pm
#23
kobold lord


Joined: Dec 15, 2010
Interests: mutant ass
Posts: 100
There should be some kind of named kamakazie dwarf. Like a grizzled kamakazie dwarf. He is spawned without any limbs and rolls around, and even though he rolls around he is incredibly fast, almost as fast as sherarax. He is pretty much immune to fire and carrys around tons of explosives. And if he runs out? No problem. He just summons up some more.
Feb 28, 2011, 9:10 pm
#24
Joined: Dec 3, 2007
Occupation: Chaos Weaver
Location: Standing between all life and death
Posts: 2,863
If you don't have any arms you can't use explosives. Still, it's an awesome idea
Uchuudonge wrote
creating stable chaos
making patterns where there should be none
sewing order into the chaos
you spit in the face of random numbers, of chaos
Jump to