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 } }