using System;
using Holo.Manager.Text;
using Holo.Virtual.Item;
using Holo.Virtual.Rooms;
using Holo.Virtual.Rooms.Pathfinding;
using Holo.Virtual.Users;
namespace Holo.Source.Virtual.Rooms.Pets
{
public class roomPet
{
#region Fields
#region talk items
///
/// Talk items
///
public static string[] dogTalk = { "Woef woef!", "Waf blaf!", "Wruf!" };
///
/// Talk items
///
public static string[] catTalk = { "Mauwwww", "Yawn!", "purr purr purr!" };
///
/// Talk items
///
public static string[] crockTalk = { "Tik tak!", "grr!!", "*bijt* gneghne!" };
#endregion
///
/// The random generator for this instance
///
private Random RND;
///
/// A virtualPetInformation instance with all kinds of information of this pet, such as it's appearance etc.
///
public virtualPetInformation Information;
///
/// The statusmanager of this pet
///
internal virtualRoomStatusManager statusManager;
private DateTime lastUserInteraction;
///
/// an indication if this pet is sleeping or not
///
private bool sleeping = false;
///
/// Indicates if this pet is interacting with other people
///
private bool interacting
{
get
{
if ((DateTime.Now - lastUserInteraction).TotalSeconds > 10)
return false;
else
return true;
}
set
{
lastUserInteraction = DateTime.Now;
}
}
///
/// The room this pet is currently in
///
private virtualRoom room;
///
/// Used for pathfinding. The X coordinate of the bot's target square in the virtual room.
///
internal int goalX;
///
/// Used for pathfinding. The Y coordinate of the bot's target square in the virtual room.
///
internal int goalY;
///
/// The last AI action timestamp
///
DateTime lastAiAction = DateTime.Now.AddSeconds(new Random((int)DateTime.Now.Ticks).Next(0,4));
///
/// The pets nest
///
internal genericItem nest
{
get
{
return room.floorItemManager.getItem(Information.ID);
}
}
///
/// The field used by the AI interface to play or interact with an item
///
internal genericItem itemTimeItem;
///
/// The x location in byte format
///
internal byte X
{
get
{
return Information.lastX;
}
set
{
Information.lastX = value;
}
}
///
/// The y location in byte format
///
internal byte Y
{
get
{
return Information.lastY;
}
set
{
Information.lastY = value;
}
}
///
/// the h location of this item
///
internal double H;
///
/// rotation
///
internal byte Z1;
///
/// Head rotation
///
internal float Z2;
///
/// The room unique ID
///
internal int roomUID;
///
/// The ID of the action this instance is currently doing
/// -1 = nothing
/// 0 = walking
/// 1 = walking to bed to sleep
/// 2 = sleeping
/// 3 = going to drink
/// 4 = going to food
/// 5 = going to toy
/// 6 = following user in room
/// 7 = waiting for next command
///
internal int actionID = -1;
///
/// The user which this item is following
///
internal virtualRoomUser followUser;
///
/// An indicator if this instance is following a person
///
internal bool following
{
get
{
return followUser != null;
}
}
#endregion
#region Constructors
///
/// Constructs a roomPet object for a given virtualPetInformation object.
///
/// The virtualPetInformation that holds the values for this room pet.
/// The room unique id of this item
/// The room this item is currently in
/// The x location of this item
/// The y location of this item
public roomPet(virtualPetInformation pInfo, int roomUID, virtualRoom room, byte X, byte Y)
{
this.statusManager = new virtualRoomStatusManager(roomUID, room);
this.Information = pInfo;
this.X = X;
this.Y = Y;
this.roomUID = roomUID;
this.room = room;
if (room.sqITEMHEIGHT[X, Y] == 0)
{
H = room.sqITEMHEIGHT[X, Y];
}
else
{
H = room.sqITEMHEIGHT[X, Y];
}
room.sqUNIT[X, Y] = true;
this.RND = new Random(Information.nature);
}
///
/// Constructs a roomPet object for a given virtualPetInformation object.
///
/// The virtualPetInformation that holds the values for this room pet.
/// The room unique id of this item
/// The room this item is currently in
public roomPet(virtualPetInformation pInfo, int roomUID, virtualRoom room)
{
this.statusManager = new virtualRoomStatusManager(roomUID, room);
this.Information = pInfo;
this.roomUID = roomUID;
this.room = room;
if (room.sqITEMHEIGHT[X, Y] == 0)
{
H = room.sqITEMHEIGHT[X, Y];
}
else
{
H = room.sqITEMHEIGHT[X, Y];
}
room.sqUNIT[X, Y] = true;
this.RND = new Random(Information.nature);
rotate();
}
#endregion
#region Methods
#region getters
///
/// Converts this virtual room pet to a string to make it appear for game clients.
///
public override string ToString()
{
fuseStringBuilder FSB = new fuseStringBuilder();
if (this.Information != null)
{
FSB.appendKeyValueParameter("i", this.roomUID);
FSB.appendKeyValueParameter("n", this.Information.ID.ToString() + Convert.ToChar(4).ToString() + this.Information.Name);
FSB.appendKeyValueParameter("f", this.Information.Figure);
FSB.appendKeyValueParameter("l", this.X + " " + this.Y + " " + this.H);
return FSB.ToString();
}
return FSB.ToString();
}
///
/// The status string of the bot, containing positions, movements, statuses (animations) etc.
///
internal string statusString
{
get
{
return roomUID + " " + X + "," + Y + "," + H.ToString().Replace(",", ".") + "," + Z1 + "," + Z2 + "/" + statusManager.ToString();
}
}
#endregion
#region Status management
///
/// Adds a status key and a value to the bot's statuses. If the status is already inside, then the previous one will be removed.
///
/// The key of the status.
/// The value of the status.
internal void addStatus(string Key, string Value)
{
statusManager.addStatus(Key, Value, 0, null, 0, 0);
}
///
/// Removes a certain status from the bot's statuses.
///
/// The key of the status to remove.
internal void removeStatus(string Key)
{
statusManager.removeStatus(Key);
}
///
/// Returns a bool that indicates if the bot has a certain status at the moment.
///
/// The key of the status to check.
internal bool containsStatus(string Key)
{
return statusManager.containsStatus(Key);
}
///
/// Adds a status to the bot, keeps it for a specified amount of time [in ms] and removes the status. Refreshes at add and remove.
///
/// The key of the status, eg, 'sit'.
/// The value of the status, eg, '1.0'.
/// The amount of milliseconsd to keep the status before removing it again.
internal void handleStatus(string Key, string Value, int Length)
{
statusManager.addStatus(Key, Value, Length, null, 0, 0);
}
#endregion
#region AI
#region AI cycle functions
///
/// Handles the bot's artifical intelligence, by interacting with users and using random values etc.
/// This is called every 410 milli seconds
///
internal void doAI()
{
if (busyWithAction())
finishAction();
else
doNewAction();
}
///
/// Finishes an action
///
private void finishAction()
{
if (!sleeping && actionID == 1 && goalX == -1)
setSleeping(true);
else if (actionID == 0 && goalX == -1)
this.actionID = -1;
else if (actionID == 3 && goalX == -1)
doDrink();
else if (actionID == 4 && goalX == -1)
doEat();
else if (actionID == 5 && goalX == -1)
doPlay();
else if (actionID == 6)
doFollow();
}
///
/// Does a new action
///
private void doNewAction()
{
int action = RND.Next(0, 7707070) % 7;
switch (action)
{
case 0: // Move - done
case 1:
walk();
break;
case 2: // Rotate - done
rotate();
break;
case 3: // Say - done
sendSaying();
break;
case 4: // Status - done
doNewStatus();
break;
case 5: // Item time - done
doItemTime();
break;
case 6: // sleep/walk to nest - done
if (RND.Next(0, 10) == 5)
goToSleep();
else
walkToNest();
break;
}
this.lastAiAction = DateTime.Now;
}
///
/// Checks if this instance is busy with an action or not
///
///
private bool busyWithAction()
{
return !(actionID == -1 && (DateTime.Now - lastAiAction).TotalSeconds > 4 && goalX == -1 && !interacting);
}
#endregion
#region AI functions
#region eating and drinking
///
/// Lets the pet drink something
///
private void doDrink()
{
this.actionID = -1;
if (itemTimeItem == null)
return;
if (!sameLocationAsItem())
return;
rotateToItem(itemTimeItem);
if (this.Information.thirsty)
{
this.Information.Thirst++;
room.sendSaying(this.roomUID, "*lik lik lik* Aahhh....");
}
else
{
if (RND.Next(0, 3) == 2)
room.sendSaying(this.roomUID, "*burp*");
}
lastAiAction = DateTime.Now;
}
///
/// Lets the pet eat something
///
private void doEat()
{
this.actionID = -1;
if (itemTimeItem == null)
return;
if (!sameLocationAsItem())
return;
lastAiAction = DateTime.Now;
this.Information.Hunger++;
rotateToItem(itemTimeItem);
if (!correctFood(itemTimeItem))
{
room.sendSaying(this.roomUID, "Blech...!");
}
else
{
if (this.Information.hungry)
{
room.petEatsItem(itemTimeItem);
this.Information.Hunger++;
if (!this.Information.hungry || !room.floorItemManager.containsItem(itemTimeItem.ID))
room.sendSaying(this.roomUID, "Hap hap hap. YUM! |");
else
this.actionID = 4;
}
else
{
if (RND.Next(0, 3) == 2)
room.sendSaying(this.roomUID, "*burp*");
}
}
}
///
/// Checks if an eatable item is ment for this ind of pet
///
/// The item which will be consumed, or not
private bool correctFood(genericItem item)
{
if (item.template.petFoodID == 0)
return true;
switch (Information.Type)
{
case 0:
return item.template.petFoodID == 1;
case 1:
return item.template.petFoodID == 2;
case 2:
return item.template.petFoodID == 3;
default:
return false;
}
}
#endregion
#region Item interaction
///
/// Lets the pet play with an item
///
private void doPlay()
{
this.actionID = -1;
if (itemTimeItem == null)
return;
if (!sameLocationAsItem())
return;
this.room.doFurniPlay(this.itemTimeItem, 3);
rotateToItem(itemTimeItem);
room.sendSaying(this.roomUID, "*pok pok pats* wii!");
this.goLay(4000);
lastAiAction = DateTime.Now;
}
///
/// does an check if the location of the pet is the same as an item (to avoid ghost-actions)
///
/// Indication if this pet is in the same location as the ItemTimeItem
private bool sameLocationAsItem()
{
return (this.X == itemTimeItem.X && this.Y == itemTimeItem.Y);
}
///
/// Rotates the head to the given item.
///
private void rotateToItem(genericItem item)
{
this.Z1 = item.Z;
this.Z2 = Z1;
}
///
/// Gets an item and the pet walks to it and tries to interact with it.
///
private void doItemTime()
{
this.itemTimeItem = room.floorItemManager.getPetRandomItem();
doItemTimeItemCheck();
}
///
/// Does an check for the current itemTimeItem
///
private void doItemTimeItemCheck()
{
if (itemTimeItem == null)
{
walk();
return;
}
this.goalX = itemTimeItem.X;
this.goalY = itemTimeItem.Y;
if (itemTimeItem.template.isPetDrink)
this.actionID = 3;
else if (itemTimeItem.template.isPetFood)
this.actionID = 4;
else if (itemTimeItem.template.isPetToy)
this.actionID = 5;
else
this.actionID = 0;
}
#endregion
#region misc
///
/// Lets this item sleep or wake up
///
/// a boolean indicating if this pet is going to sleep or not
public void setSleeping(bool value)
{
if (value)
{
if (X == this.nest.X && Y == this.nest.Y)
{
this.Z1 = (byte)RND.Next(1, 8);
this.Z2 = this.Z1;
statusManager.addStatus("slp", H.ToString(), 0, null, 0, 0);
room.sendSaying(roomUID, "ZZZzZZzzZZzz...");
Information.dtLastNap = DateTime.Now;
this.sleeping = true;
this.actionID = 2;
}
else
{
this.actionID = -1;
}
}
else
{
this.actionID = -1;
int amountSlept = (int)Math.Round((DateTime.Now - Information.dtLastNap).TotalMinutes / 1);
//Out.WriteLine(amountSlept.ToString());
statusManager.Clear();
room.sendSaying(roomUID, "|| *gaap* ||");
this.sleeping = false;
}
}
///
/// Goes to sleep
///
private void goToSleep()
{
walkToNest();
actionID = 1;
}
///
/// Walks this pet to its nest
///
private void walkToNest()
{
goalX = nest.X;
goalY = nest.Y;
}
#endregion
#endregion
#endregion
#region new status
///
/// Lets a pet wake up
///
internal void wakeUp()
{
setSleeping(false);
}
///
/// Does a new status (sit/lay/sleep)
///
internal void doNewStatus()
{
if (statusManager.containsStatus("sit"))
return;
switch (RND.Next(1, 3))
{
case 1:
goSit(4000);
break;
case 2:
goLay(4000);
break;
case 3:
goJump(2000);
break;
}
}
///
/// Lets this instance sit down
///
/// The total duration in milli seconds
internal void goSit(int duration)
{
removeAllStatusses();
handleStatus("sit", H.ToString(), duration);
}
///
/// Clears all statusses of this instance
///
private void removeAllStatusses()
{
statusManager.Clear();
}
///
/// Lets this instance lay on the floor
///
/// The total duration in milli seconds
internal void goLay(int duration)
{
removeAllStatusses();
handleStatus("lay", H.ToString(), duration);
}
///
/// Lets this instance jump on the floot
///
/// The total duration in milli seconds
internal void goJump(int duration)
{
removeAllStatusses();
handleStatus("jmp", Z1.ToString(), duration);
}
#endregion
#region rotation / walking
///
/// Rotates a pet
///
private void rotate()
{
if (containsStatus("sit") || containsStatus("lay"))
return;
byte R = (byte)(RND.Next(0, 1000) % 11);
while (R == Z2)
R = (byte)(RND.Next(0, 1000) % 11);
Rotate(R);
}
///
/// Sets a new rotation for the bot and refreshes it in the room. If the bot is sitting, then rotating will be ignored.
///
/// The new rotation to use.
internal void Rotate(byte R)
{
if (R != Z1 && !containsStatus("sit") && !containsStatus("lay"))
{
Z1 = R;
Z2 = R;
}
}
///
/// Walks to a new random coordinate
///
internal void walk()
{
Coord Next = new Coord();
int[] Borders = room.getMapBorders();
Next = new Coord(RND.Next(0, Borders[0]), RND.Next(0, Borders[1]));
if (Next.X == room.doorX && Next.Y == room.doorY)
return;
if (Next.X == X && Next.Y == Y) // Coord didn't changed
{
Z1 = (byte)RND.Next(0, 10);
Z2 = Z1;
}
else
{
goalX = Next.X;
goalY = Next.Y;
this.actionID = 0;
}
}
#endregion
#region chatting
///
/// Sends a new saying to this room
///
internal void sendSaying()
{
int x = RND.Next(0,30000) % 3;
switch (Information.Type)
{
case 0:
room.sendSaying(roomUID, dogTalk[x]);
break;
case 1:
room.sendSaying(roomUID, catTalk[x]);
break;
case 2:
room.sendSaying(roomUID, crockTalk[x]);
break;
}
}
#endregion
#endregion
#region user interaction
///
/// Does an action based on user speech
///
/// The action in string format
private bool listenAction(string action)
{
switch (action)
{
case "eet":
this.itemTimeItem = room.floorItemManager.getPetFoodItem(Information.Type + 1);
doItemTimeItemCheck();
return true;
case "drink":
this.itemTimeItem = room.floorItemManager.getPetDrinkItem();
doItemTimeItemCheck();
return true;
}
return false;
}
///
/// Does an action with user controll
///
/// The action which may be excecuted
/// The user which send the command (owner only)
internal void doAction(string action, virtualRoomUser user)
{
interacting = true;
if (this.sleeping)
{
wakeUp();
return;
}
this.Z1 = Holo.Virtual.Rooms.Pathfinding.Rotation.Calculate(X, Y, user.X, user.Y);
this.Z2 = Z1;
if (listenAction(action))
return;
if (!wantsToListen())
return;
switch (action)
{
case "zit":
this.goSit(0);
break;
case "lig":
this.goLay(0);
break;
case "spring":
this.goJump(2000);
break;
case "kom":
this.goalX = user.lastX;
this.goalY = user.lastY;
this.sendSaying();
this.actionID = 0;
return;
case "speeltje":
this.itemTimeItem = room.floorItemManager.getPetPlayItem();
doItemTimeItemCheck();
break;
case "volg":
follow(user);
break;
case "blijf":
doStay();
break;
case "mand":
walkToNest();
break;
case "slaap":
goToSleep();
break;
}
}
///
/// Lets the pet stay in his current place, while sitting
///
private void doStay()
{
this.actionID = 7;
this.goSit(0);
}
///
/// Follows a user
///
/// the user which wants to be followed
private void follow(virtualRoomUser user)
{
if (following)
{
if (user == followUser)
{
followUser = null;
actionID = -1;
}
else
followUser = user;
}
else
{
followUser = user;
actionID = 6;
}
}
///
/// Follows the user
///
private void doFollow()
{
lastAiAction = DateTime.Now;
actionID = -1;
if(following)
{
if (room.containsUser(followUser.roomUID))
{
if (room.getRoomUser(followUser.roomUID) == followUser)
{
this.goalX = followUser.lastX;
this.goalY = followUser.lastY;
actionID = 6;
}
}
}
}
///
/// Does a % calculation if this pet wants to obey his master
/// based on the friendship
///
///
private bool wantsToListen()
{
if (RND.Next(0, 11) <= Information.Friendship)
return true;
room.sendSaying(roomUID, "grmpf");
walk();
return false;
}
#endregion
}
}