// lmpperfmisc // Miscellaneous functions for the Performer NPC's. // Change this to set the interval between performances. int nHoursBetweenPerformances = 2; // State transitions (User-defined event numbers) int stPerformerMin = 5001; int stPerformerDoneMoveToPerform = 5001; int stPerformerDoneMoveHome = 5002; int stPerformerMax = 5099; void MoveToPerformPoint(); void StartPerform(); void Perform(); void PerformMagic(object oOtherGuy); void PerformSong(object oOtherGuy); void TerminatePerformBoth(); object GetMyChair(); void DonClothes(); void lmpDebugMsg(string st, int n = 99999) { /* REMOVED to hide debugging messages string stMsg; stMsg = GetName(OBJECT_SELF); stMsg = stMsg + ": "; stMsg = stMsg + st; if (n != 99999) { stMsg = stMsg + ": "; stMsg = stMsg + IntToString(n); } SendMessageToAllDMs(stMsg); object oPC = GetFirstPC(); if (oPC != OBJECT_INVALID) SendMessageToPC(oPC, stMsg); */ } object GetOtherGuy() { if (GetTag(OBJECT_SELF) == "lmpPerformer1") { return GetNearestObjectByTag("lmpPerformer2"); } else { return GetNearestObjectByTag("lmpPerformer1"); } } int IsPerformer1() { return (GetTag(OBJECT_SELF) == "lmpPerformer1"); } object GetNearbyPC() { return GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC); } // STATE TRANSITIONS // The character states are: // 0: Idle waiting for next performance // 1: Moving to performance point // 2: Performing // 3: Moving back to idle point int GetCurrentState() { return GetLocalInt(OBJECT_SELF, "PerfState"); } void SetCurrentState(int n) { SetLocalInt(OBJECT_SELF, "PerfState", n); lmpDebugMsg("Set state", n); } void SignalStateTransition(int st) { ActionDoCommand(SignalEvent(OBJECT_SELF, EventUserDefined(st))); } void OnHeartbeatUpdatePerformerState() { // Check times for various daily actions. Check from latest to earliest, // so that actions are skipped if it's too late for them (e.g. when module // starts up). int nState; int nHour; nState = GetCurrentState(); nHour = GetTimeHour(); if (nHour < 6) { // reset cycle while sleeping if (nState > 0) SetCurrentState(0); SetLocalInt(OBJECT_SELF, "lmpPerformLastTime", 0); } else if (nState == 0 && nHour >= 9 && GetIsDay()) { int nLastTime = GetLocalInt(OBJECT_SELF, "lmpPerformLastTime"); if (nHour - nLastTime >= nHoursBetweenPerformances) { // Showtime! Get moving. StartPerform(); //SetCurrentState(1); //SetLocalInt(OBJECT_SELF, "lmpPerformLastTime", nHour); //MoveToPerformPoint(); } } } void PerformerDoneMoveToPerform() { // Arrived at performance point. Start performing. lmpDebugMsg("User-defined event DoneMoveToPerform"); int nState = GetCurrentState(); if (nState != 1) { lmpDebugMsg("ERROR: TerminatePerform: Invalid state", nState); } else { SetCurrentState(2); // Perform! until further notice Perform(); } } void PerformerDoneMoveHome() { // Arrived home. Go to sleep or something. lmpDebugMsg("User-defined event DoneMoveHome"); int nState = GetCurrentState(); if (nState != 3) { lmpDebugMsg("ERROR: TerminatePerform: Invalid state", nState); } SetCurrentState(0); SetLocalInt(OBJECT_SELF, "lmpPerformLastTime", GetTimeHour()); // KLUDGE: This isn't the best time to do this, but there is no best time: if (!IsPerformer1() && GetLocalInt(OBJECT_SELF, "lmpPerform")) DonClothes(); // If there's a chair nearby, sit in it. Else sit on the ground. object oChair = GetMyChair(); if (oChair != OBJECT_INVALID) { ActionSit(oChair); } else { ActionPlayAnimation(ANIMATION_LOOPING_SIT_CROSS, 1.0, 9999.0); } // BUG: Can't put the character to sleep, because after he wakes up he // doesn't respond to any Action commands!??! /*effect effSleep = EffectSleep(); effect effVis = EffectVisualEffect(VFX_IMP_SLEEP); effect eff = EffectLinkEffects(effSleep, effVis); ApplyEffectToObject(DURATION_TYPE_PERMANENT, eff, OBJECT_SELF);*/ } // Thanks to Rhodan. int RemoveEffectType(object oTarget,int nEffectType) { int nRemoved=FALSE; int nFound=0; effect eEffect; lmpDebugMsg("RemoveEffectType: Looking for effect", nEffectType); if (GetIsObjectValid(oTarget)) { eEffect=GetFirstEffect(oTarget); if (GetIsEffectValid(eEffect)) { do { nFound=GetEffectType(eEffect); // uncomment the next line if you want to see all the effects found lmpDebugMsg("RemoveEffectType: Found effect", nFound); if (nFound==nEffectType) { RemoveEffect(oTarget, eEffect); // NOT Broken lmpDebugMsg("RemoveEffectType: Removed effect", nFound); nRemoved=TRUE; } eEffect=GetNextEffect(oTarget); } while (GetIsEffectValid(eEffect)); } } else { nRemoved=FALSE; } lmpDebugMsg("RemoveEffectType: Done", nRemoved); return nRemoved; } object GetMyWaypoint(string sType) { string sTag; object oWaypoint; sTag = GetTag(OBJECT_SELF); sTag = sTag + sType; return GetNearestObjectByTag(sTag); } void MoveToMyWaypoint(string sType) { object oWaypoint = GetMyWaypoint(sType); if (GetIsObjectValid(oWaypoint)) { lmpDebugMsg("MoveToMyWaypoint: moving"); //ClearAllActions(); ActionMoveToObject(oWaypoint); } else { lmpDebugMsg("MoveToMyWaypoint: No waypoint"); } } void MoveToPerformPoint() { // BUG: Even though RemoveEffectType removes the sleep effect, and the // character stands up, no further actions can be performed! MoveToMyWaypoint // does absolutely nothing if called after the character wakes up. //RemoveEffectType(OBJECT_SELF, EFFECT_TYPE_SLEEP); ClearAllActions(); // NOTE: Change this if the performer isn't sitting while resting! // BUG: There's no good way to interrupt a looping animation! // Without the following two lines, the performer will keep trying // to sit down during the performance!! ActionPlayAnimation(ANIMATION_LOOPING_PAUSE, 1.0, 1.0); ActionWait(2.0); lmpDebugMsg("MoveToPerformPoint: ready to move"); MoveToMyWaypoint("Perform"); SignalStateTransition(stPerformerDoneMoveToPerform); } void MoveHome() { //ClearAllActions(); MoveToMyWaypoint("Home"); ActionDoCommand(SetFacingPoint(GetPosition(GetMyWaypoint("Perform")))); SignalStateTransition(stPerformerDoneMoveHome); } object GetMyChair() { // Return a empty chair *or* stool nearby. float dChair = 9999.0; object oChair = GetNearestObjectByTag("Chair"); if (oChair != OBJECT_INVALID && GetSittingCreature(oChair) == OBJECT_INVALID) dChair = GetDistanceToObject(oChair); float dStool = 9999.0; object oStool = GetNearestObjectByTag("Stool"); if (oStool != OBJECT_INVALID && GetSittingCreature(oStool) == OBJECT_INVALID) dStool = GetDistanceToObject(oStool); if (dChair <= dStool && dChair < 2.0) return oChair; else if (dStool < 2.0) return oStool; else return OBJECT_INVALID; } void FacePC() { // Face a near-by PC. object oPC = GetNearbyPC(); if (oPC != OBJECT_INVALID && GetDistanceToObject(oPC) <= 10.0) SetFacingPoint(GetPosition(oPC)); } void FacePCBoth() { // Tell both performers to face a near-by PC. object oOtherGuy = GetOtherGuy(); if (oOtherGuy != OBJECT_INVALID) AssignCommand(oOtherGuy, FacePC()); FacePC(); } void SpeakOneLine(int iLine) { SetLocalInt(OBJECT_SELF, "lmpPerformOneLiner", iLine); SpeakOneLinerConversation("lmpperfoneliners"); } void StartPerform() { if (GetCurrentState() != 0) { lmpDebugMsg("StartPerformBoth: Invalid state"); } else { SetCurrentState(1); SetLocalInt(OBJECT_SELF, "lmpPerformLastTime", GetTimeHour()); MoveToPerformPoint(); } } void Perform() { int fIsPerformer1 = IsPerformer1(); object oOtherGuy = GetOtherGuy(); lmpDebugMsg("Perform!"); if (fIsPerformer1) { SpeakOneLine(1); } ActionPlayAnimation(ANIMATION_FIREFORGET_BOW); // NOTE: lmpPerformer1 is the one who coordinates the performance for both // performers. The same performance type (lmpPerform) must be set for // both performers! if (fIsPerformer1) { int nPerform = Random(5) + 1; SetLocalInt(OBJECT_SELF, "lmpPerform", nPerform); SetLocalInt(oOtherGuy, "lmpPerform", nPerform); lmpDebugMsg("Perform: nPerform", nPerform); switch (nPerform) { case 1: case 4: PerformMagic(oOtherGuy); break; case 2: case 3: case 5: PerformSong(oOtherGuy); break; } } } void PerformMagic(object oOtherGuy) { SetLocalInt(OBJECT_SELF, "lmpPerfAct", 1); if (oOtherGuy != OBJECT_INVALID) { SetLocalInt(OBJECT_SELF, "lmpBeginPerform", 1); ActionStartConversation(oOtherGuy); } } void ContinuePerformance(int nAct) { SetLocalInt(OBJECT_SELF, "lmpPerfAct", 2); SetLocalInt(OBJECT_SELF, "lmpBeginPerform", 1); ActionStartConversation(GetOtherGuy()); } void PerformRabbitTrick() { effect eff = EffectSummonCreature("badger001", VFX_FNF_SUMMON_MONSTER_1); ActionPlayAnimation(ANIMATION_LOOPING_GET_LOW, 1.0, 3.0); ActionDoCommand(ApplyEffectToObject(DURATION_TYPE_PERMANENT, eff, OBJECT_SELF)); ActionDoCommand(ContinuePerformance(2)); } void DonClothes() { object oClothes = GetItemPossessedBy(OBJECT_SELF, "lmpPerfCostume"); if (oClothes != OBJECT_INVALID) { ActionEquipItem(oClothes, INVENTORY_SLOT_CHEST); lmpDebugMsg("DonClothes: Done"); } else { lmpDebugMsg("DonClothes: ERROR: No clothes!"); } } void PerformInvisTrick() { ActionWait(1.0); ActionCastFakeSpellAtObject(SPELL_INVISIBILITY, OBJECT_SELF); object oClothes = GetItemPossessedBy(OBJECT_SELF, "lmpPerfCostume"); if (oClothes != OBJECT_INVALID) { ActionUnequipItem(oClothes); lmpDebugMsg("PerformInvisTrick: Un-equiped"); } ActionWait(1.0); ActionDoCommand(ContinuePerformance(2)); } void PerformInvisTrick2() { MoveToMyWaypoint("Home"); //DonClothes(); } void PerformSong(object oOtherGuy) { FacePC(); effect eff = EffectVisualEffect(VFX_DUR_BARD_SONG); ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eff, OBJECT_SELF, 30.0); ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eff, oOtherGuy, 30.0); SetLocalInt(OBJECT_SELF, "lmpBeginPerform", 1); ActionStartConversation(oOtherGuy);//GetNearbyPC()); //DelayCommand(20.0, TerminatePerformBoth()); } void TerminatePerform() { // Clean up any variables, effects, states, etc. int nPerform = GetLocalInt(OBJECT_SELF, "lmpPerform"); switch (nPerform) { case 2: // BUG: This doesn't actually work (see above). //RemoveEffectType(OBJECT_SELF, EFFECT_TYPE_SLEEP); break; case 4: // prefer to do this later //DonClothes(); break; } // Go home. int nState = GetCurrentState(); if (nState != 2 && nState != 6 && nState != 10) { lmpDebugMsg("ERROR: TerminatePerform: Invalid state", nState); } else { SetCurrentState(nState + 1); FacePC(); ActionPlayAnimation(ANIMATION_FIREFORGET_BOW); ActionWait(2.0); } MoveHome(); } void TerminatePerformBoth() { // Tell both performers to go home. object oOtherGuy = GetOtherGuy(); if (oOtherGuy != OBJECT_INVALID) AssignCommand(oOtherGuy, TerminatePerform()); TerminatePerform(); } void StartPerformBoth() { // Tell both performers to start performing. object oOtherGuy = GetOtherGuy(); if (oOtherGuy != OBJECT_INVALID) AssignCommand(oOtherGuy, StartPerform()); StartPerform(); } void OnPurseStolen(object oThief) { lmpDebugMsg("OnPurseStolen: Called"); // Check if I can see the thief. // BUG (maybe): It seems that GetObjectSeen *always* sees a rogue or ranger // who is hiding in shadows. Invisibility spell or potion makes the thief // invisible though. if (!GetObjectSeen(oThief)) // DEBUG lmpDebugMsg("OnPurseStolen: Not seen."); if (GetDistanceBetween(OBJECT_SELF, oThief) < 40.0 && GetObjectSeen(oThief)) { lmpDebugMsg("OnPurseStolen: Caught!!!"); ClearAllActions(); // Performer 1 utters an exclamation and Performer 2 converses with the // thief. if (IsPerformer1()) { SpeakOneLine(2); } else { SetLocalInt(OBJECT_SELF, "lmpPerformTheft", 1); SetLocalObject(OBJECT_SELF, "lmpPerformThief", oThief); ActionStartConversation(oThief); } } lmpDebugMsg("OnPurseStolen: Done."); } void SignalPurseStolenBoth(object oThief) { //lmpDebugMsg("SignalPurseStolenBoth: THIEF!"); object oNPC = GetNearestObjectByTag("lmpPerformer1"); if (oNPC != OBJECT_INVALID) AssignCommand(oNPC, OnPurseStolen(oThief)); oNPC = GetNearestObjectByTag("lmpPerformer2"); if (oNPC != OBJECT_INVALID) AssignCommand(oNPC, OnPurseStolen(oThief)); }