// This is a library of functions used in the global event system // and NPC rumor system. #include "nwnx_files" #include "events_inc" //Get the town's name from a campaign variable string TownName(string sData); //Select town randomly string RandomTown(); //Make a quest available for a PC void MakeQuestAvailable(object oPC); //Spawn invasion void SpawnInvasion(string sTown, int nLevel); //Spawn festival void SpawnFestival(string sTown); //Spawn special void SpawnSpecial(string sTown); //Delete invasion data void DeleteInvasion(string sTown); //Delete festival data void DeleteFestival(string sTown); //Delete special event data void DeleteSpecial(string sTown); //A pseudo-heartbeat script that should be fired at module's initialization; suggested intervals: 600.0 (10 minutes) void GlobalEvents(float fInterval=600.0); //Get the town's name from a campaign variable string TownName(string sData) { if (sData == "ALVERTON") return "Alverton"; //TEST - to be added more return ""; } //Check if the special event should end and do so if it should void CheckCurrentEvents(string sTown) { string sDB = GetLocalString(GetModule(), "DB"); string sMode = GetLocalString(GetModule(), "MODE"); int nEventTime = 90; int nDestroyedTime = 120; if (sMode == "ONLINE") { nEventTime = 2*nEventTime; nDestroyedTime = 2*nDestroyedTime; } if (GetCampaignInt(sDB, sTown+"_FESTIVAL") == TRUE) { int nDifference = CurrentDateTime() - GetCampaignInt(sDB, sTown+"_EVENT_START"); if (2*nDifference > nEventTime) DeleteFestival(sTown); //delete event } if (GetCampaignInt(sDB, sTown+"_SPECIAL") == TRUE) { int nDifference = CurrentDateTime() - GetCampaignInt(sDB, sTown+"_EVENT_START"); if (2*nDifference > nEventTime) DeleteSpecial(sTown); //delete event } if (GetCampaignInt(sDB, sTown+"_DESTROYED") == TRUE) { int nDifference = CurrentDateTime() - GetCampaignInt(sDB, sTown+"_EVENT_START"); if (2*nDifference > nDestroyedTime) { DeleteCampaignVariable(sDB, sTown+"_DESTROYED"); DeleteCampaignVariable(sDB, sTown+"_EVENT_START"); //delete event } } if (GetCampaignString(sDB, sTown+"_INV_MON") != "") { int nDifference = CurrentDateTime() - GetCampaignInt(sDB, sTown+"_EVENT_START"); if (2*nDifference > 40) { if (Random(2) == 0) //50% chances of the town being destroyed in an unrepelled invasion { SetCampaignInt(sDB, sTown+"_DESTROYED", TRUE); int nBonus = -2; int nEconomy = GetCampaignInt(sDB, sTown+"_ECONOMY"); if (nEconomy+nBonus < 1) SetCampaignInt(sDB, sTown+"_ECONOMY", 1); else SetCampaignInt(sDB, sTown+"_ECONOMY", nEconomy+nBonus); DeleteInvasion(sTown); //delete event SetCampaignInt(sDB, sTown+"_EVENT_START", CurrentDateTime()); } else { DeleteInvasion(sTown); //delete event } } } } //Select town randomly string RandomTown() { string sTown; switch (Random(1)) //TEST - to be expanded { case 0: sTown = "ALVERTON"; break; } return sTown; } //Make a quest available for a PC void MakeQuestAvailable(object oPC) { string sDB = CharacterDB(oPC); if (/*GetCampaignInt(sDB, "QUEST_ACTIVE") == TRUE ||*/ GetCampaignInt(sDB, "QUEST_AVAILABLE") == TRUE) return; SetCampaignInt(sDB, "QUEST_AVAILABLE", TRUE); } //Delete invasion data void DeleteInvasion(string sTown) { string sDB = GetLocalString(GetModule(), "DB"); DeleteCampaignVariable(sDB, sTown+"_INV_LVL"); DeleteCampaignVariable(sDB, sTown+"_INV_MON"); DeleteCampaignVariable(sDB, sTown+"_EVENT_START"); } //Delete festival data void DeleteFestival(string sTown) { string sDB = GetLocalString(GetModule(), "DB"); DeleteCampaignVariable(sDB, sTown+"_FESTIVAL"); DeleteCampaignVariable(sDB, sTown+"_EVENT_START"); } //Delete special event data void DeleteSpecial(string sTown) { string sDB = GetLocalString(GetModule(), "DB"); DeleteCampaignVariable(sDB, sTown+"_SPECIAL"); DeleteCampaignVariable(sDB, sTown+"_EVENT_START"); } //Spawn invasion void SpawnInvasion(string sTown, int nLevel) { string sMonster; switch (Random(5)) { case 0: sMonster = "orc"; break; case 1: sMonster = "und"; break; case 2: sMonster = "rep"; break; case 3: sMonster = "dem"; break; case 4: sMonster = "gia"; break; } string sDB = GetLocalString(GetModule(), "DB"); SetCampaignInt(sDB, sTown+"_INV_LVL", nLevel); SetCampaignString(sDB, sTown+"_INV_MON", sMonster); DeleteCampaignVariable(sDB, sTown+"_INV_NUM"); //0 invaders killed DeleteCampaignVariable(sDB, sTown+"_INV_DEF"); //0 defenders killed SetCampaignInt(sDB, sTown+"_EVENT_START", CurrentDateTime()); //we store game date for LOCAL games object oArea = GetArea(GetWaypointByTag(sTown+"_INV_WP")); object oObject = GetFirstObjectInArea(oArea); //let's destroy all creatures (except for PCs and their henchmen) in the invaded town area, //just in case some remain from a previous invasion while (GetIsObjectValid(oObject)) { if (!GetIsPC(oObject) && !GetIsPC(GetMaster(oObject)) && GetObjectType(oObject) == OBJECT_TYPE_CREATURE) DestroyObject(oObject); oObject = GetNextObjectInArea(oArea); } GlobalMessage(TownName(sTown) + " is under attack!"); } //Spawn festival void SpawnFestival(string sTown) { string sDB = GetLocalString(GetModule(), "DB"); SetCampaignInt(sDB, sTown+"_FESTIVAL", TRUE); /*if (GetLocalString(GetModule(), "MODE") != "ONLINE")*/ SetCampaignInt(sDB, sTown+"_EVENT_START", CurrentDateTime()); //we store game date for LOCAL games //else SetCampaignInt(sDB, sTown+"FES_START", GetSystemTime()); //for ONLINE games we store system date object oNPC = CreateObject(OBJECT_TYPE_CREATURE, "anc_ultimatemerc", GetLocation(GetWaypointByTag(sTown+"_ultmerch"))); SetLocalString(oNPC, "Town", sTown); int nBonus = 1; int nEconomy = GetCampaignInt(sDB, sTown+"_ECONOMY"); if (nEconomy+nBonus > 9) SetCampaignInt(sDB, sTown+"_ECONOMY", 9); else if (nEconomy+nBonus < 1) SetCampaignInt(sDB, sTown+"_ECONOMY", 1); else SetCampaignInt(sDB, sTown+"_ECONOMY", nEconomy+nBonus); GlobalMessage("A festival in " + TownName(sTown) + " has begun!"); } //Spawn special void SpawnSpecial(string sTown) { string sDB = GetLocalString(GetModule(), "DB"); SetCampaignInt(sDB, sTown+"_SPECIAL", TRUE); /*if (GetLocalString(GetModule(), "MODE") != "ONLINE")*/ SetCampaignInt(sDB, sTown+"_EVENT_START", CurrentDateTime()); //we store game date for LOCAL games //else SetCampaignInt(sDB, sTown+"SPE_START", GetSystemTime()); //for ONLINE games we store system date GlobalMessage("There is an event taking place in " + TownName(sTown) + "!"); } //Chooses the oldest rumor slot to overwrite int OldestRumorSlot() { string sDB = GetLocalString(GetModule(), "DB"); int nSlot = GetCampaignInt(sDB, "RUMOR_LAST"); if (nSlot == 3) nSlot = 1; else nSlot++; SetCampaignInt(sDB, "RUMOR_LAST", nSlot); return nSlot; } //A pseudo-heartbeat script that should be fired at module's initialization; suggested intervals: 300.0 (5 minutes) void GlobalEvents(float fInterval=300.0) { //Repeat DelayCommand(fInterval, GlobalEvents(fInterval)); //stop if there is no PC in the game if (!GetIsObjectValid(GetFirstPC())) return; //let's find the highest-levelled PC in-game int nHighestLvl = 1; int nLevel; string sDB = GetLocalString(GetModule(), "DB"); object oPC = GetFirstPC(); object oStrongest = oPC; while (GetIsObjectValid(oPC)) { nLevel = GetHitDice(oPC); if (nLevel > nHighestLvl) { nHighestLvl = nLevel; oStrongest = oPC; } oPC = GetNextPC(); } //Let's check the area of Green Road object oArea = GetArea(GetWaypointByTag("GROAD_SP_1")); object oObject = GetFirstObjectInArea(oArea); int nEmpty = TRUE; while (GetIsObjectValid(oObject)) { if (GetObjectType(oObject) == OBJECT_TYPE_CREATURE) { nEmpty = FALSE; break; } oObject = GetNextObjectInArea(oArea); } if (nEmpty == TRUE && Random(10) == 0) { int nLvl = Random(nHighestLvl)+1; if (nLvl > 20) nLvl = Random(20)+1; SelectEnemies("1", nLvl, oStrongest, ENCOUNTER_TYPE_NORMAL); //Select possible enemies string sEnemy1 = GetLocalString(oStrongest, "sEnemy1"); string sEnemy2 = GetLocalString(oStrongest, "sEnemy2"); string sEnemy3 = GetLocalString(oStrongest, "sEnemy3"); string sEnemy4 = GetLocalString(oStrongest, "sEnemy4"); string sEnemy5 = GetLocalString(oStrongest, "sEnemy5"); string sEnemy6 = GetLocalString(oStrongest, "sEnemy6"); DeleteEnemyVar(oStrongest); object oCreature = CreateObject(OBJECT_TYPE_CREATURE, SelectMain(sEnemy1, sEnemy2, sEnemy3, sEnemy4, sEnemy5, sEnemy6), GetLocation(GetWaypointByTag("GROAD_SP_1"))); ScaleCreature(oCreature, nLvl); AssignCommand(oCreature, ActionRandomWalk()); oCreature = CreateObject(OBJECT_TYPE_CREATURE, SelectMain(sEnemy1, sEnemy2, sEnemy3, sEnemy4, sEnemy5, sEnemy6), GetLocation(GetWaypointByTag("GROAD_SP_2"))); ScaleCreature(oCreature, nLvl); AssignCommand(oCreature, ActionRandomWalk()); oCreature = CreateObject(OBJECT_TYPE_CREATURE, SelectMain(sEnemy1, sEnemy2, sEnemy3, sEnemy4, sEnemy5, sEnemy6), GetLocation(GetWaypointByTag("GROAD_SP_3"))); ScaleCreature(oCreature, nLvl); AssignCommand(oCreature, ActionRandomWalk()); } //Now we choose a town to which a random event may apply string sTown = RandomTown(); //We don't want any new event if there's a special event in progress or if the town is destroyed if (GetCampaignString(sDB, sTown+"_INV_MON") != "") return; if (GetCampaignInt(sDB, sTown+"_FESTIVAL") == TRUE) return; if (GetCampaignInt(sDB, sTown+"_SPECIAL") == TRUE) return; if (GetCampaignInt(sDB, sTown+"_DESTROYED") == TRUE) return; //Now we can decide what kind of an event we want, if any int nEvent; int nRandom = Random(100)+1; if (nRandom <= 45) nEvent = 0; //no event - 45% else if (nRandom <= 80) nEvent = 1; //insignificant event (mainly for rumors) - 35% else if (nRandom <= 94) nEvent = 2; //invisible (but significant) event - 14% else if (nRandom <= 97) nEvent = 3; //adventurer's death or arrival - 3% else if (nRandom <= 100) nEvent = 4; //special event - 3% //Now we create this event string sEvent; if (nEvent == 0) return; //no event, we're done here if (nEvent == 1) //insignificant event { nRandom = Random(20); if ((GetCampaignInt(sDB, "1_ADV_OCCUPIED") == FALSE && nRandom == 1) || (GetCampaignInt(sDB, "2_ADV_OCCUPIED") == FALSE && nRandom == 2) || (GetCampaignInt(sDB, "3_ADV_OCCUPIED") == FALSE && nRandom == 3) || ((GetCampaignInt(sDB, "1_ADV_OCCUPIED") == FALSE || GetCampaignInt(sDB, "2_ADV_OCCUPIED") == FALSE) && nRandom == 17) || ((GetCampaignInt(sDB, "1_ADV_OCCUPIED") == FALSE || GetCampaignInt(sDB, "3_ADV_OCCUPIED") == FALSE) && nRandom == 18) || ((GetCampaignInt(sDB, "2_ADV_OCCUPIED") == FALSE || GetCampaignInt(sDB, "3_ADV_OCCUPIED") == FALSE) && nRandom == 19)) switch (Random(14)) { case 0: sEvent = "island"; break; //some ship encountered an unknown island case 1: sEvent = "north"; break; //another explorer has gone to the north and hasn't returned case 2: sEvent = "drow"; break; //elves captured a drow spy in Serine Derthore case 3: sEvent = "nuvaris"; break; //the city of Nuvaris has very dynamic politics, a new ruler might emerge soon case 4: sEvent = "underdark"; break; //more drow and duergar sightings on the surface, possibility of invasion case 5: sEvent = "tomb"; break; //explorer discovered a tomb in Shahtir desert and was ripped apart by a mummy case 6: sEvent = "necromancer"; break; //sightings of undead in Green Coast, a necromancer might be gathering troops case 7: sEvent = "ghoul"; break; //a travelling merchant was assaulted by a ghoul while traversing Corrupted Wetlands case 8: sEvent = "artifact"; break; //there's an ancient artifact hidden somewhere that's said to be powerful beyond imagination case 9: sEvent = "masked"; break; //a masked figure has been seen throughout the realm, wears a skull mask, might be a lich case 10: sEvent = "dragons"; break; //there was a battle of dragons above the Grey Peaks, not known which dragon won case 11: sEvent = "barbarian"; break; //barbarian started a tavern brawl in the town, hardly unexpected case 12: sEvent = "friends"; break; //there was an adventurer party visiting with a dwarf and an elf who seemed friends, surprise case 13: sEvent = "orcs"; break; //orcs spotted near the town, which is concerning case 14: sEvent = "wizard"; break; //a wizard tried to time travel to the past and disappeared completely case 15: sEvent = "nymph"; break; //a friend travelling through the forest heard whispers encouraging him to go off the road; he suspects it was a nymph case 16: sEvent = "caravan"; break; //a caravan has been attacked, but was guarded } else switch (nRandom) { case 0: sEvent = "island"; break; //some ship encountered an unknown island case 1: sEvent = "famous1"; break; //activity of the first famous adventurer - dependant on their alignment case 2: sEvent = "famous2"; break; //activity of the second famous adventurer case 3: sEvent = "famous3"; break; //activity of the third famous adventurer case 4: sEvent = "north"; break; //another explorer has gone to the north and hasn't returned case 5: sEvent = "drow"; break; //elves captured a drow spy in Serine Derthore case 6: sEvent = "nuvaris"; break; //the city of Nuvaris has very dynamic politics, a new ruler might emerge soon case 7: sEvent = "underdark"; break; //more drow and duergar sightings on the surface, possibility of invasion case 8: sEvent = "tomb"; break; //explorer discovered a tomb in Shahtir desert and was ripped apart by a mummy case 9: sEvent = "necromancer"; break; //sightings of undead in Green Coast, a necromancer might be gathering troops case 10: sEvent = "ghoul"; break; //a travelling merchant was assaulted by a ghoul while traversing Corrupted Wetlands case 11: sEvent = "artifact"; break; //there's an ancient artifact hidden somewhere that's said to be powerful beyond imagination case 12: sEvent = "masked"; break; //a masked figure has been seen throughout the realm, wears a skull mask, might be a lich case 13: sEvent = "dragons"; break; //there was a battle of dragons above the Grey Peaks, not known which dragon won case 14: sEvent = "barbarian"; break; //barbarian started a tavern brawl in the town, hardly unexpected case 15: sEvent = "friends"; break; //there was an adventurer party visiting with a dwarf and an elf who seemed friends, surprise case 16: sEvent = "orcs"; break; //orcs spotted near the town, which is concerning case 17: sEvent = "wizard"; break; //a wizard tried to time travel to the past and disappeared completely case 18: sEvent = "nymph"; break; //a friend travelling through the forest heard whispers encouraging him to go off the road; he suspects it was a nymph case 19: sEvent = "caravan"; break; //a caravan has been attacked, but was guarded } int nAlignment; string sEventData1; if (sEvent == "famous1") { nAlignment = GetCampaignInt(sDB, "1_ADV_GOODAXIS"); if (nAlignment == ALIGNMENT_GOOD) sEvent = "good"; //some story detailing the adventurer's heroic deeds else if (nAlignment == ALIGNMENT_EVIL) sEvent = "evil"; //some story detailing the adventurer's evil deeds else if (nAlignment == ALIGNMENT_NEUTRAL) sEvent = "neutral"; //some story detailing the adventurer's deeds that might be seen as evil or good sEvent = IntToString(Random(2)+1) + sEvent; //two variants for each: 1good - rescued a merchant from bandits, 2good - took no reward for fending off an assault on a village, 1evil - used a passerby as a living shield in combat, 2evil - murdered a merchant, 1neutral - 1good, 2neutral - 1evil if (sEvent == "1neutral") sEvent = "1good"; if (sEvent == "2neutral") sEvent = "1evil"; sEventData1 = GetCampaignString(sDB, "1_ADV_NAME") + " " + GetCampaignString(sDB, "1_ADV_LASTNAME"); } if (sEvent == "famous2") { nAlignment = GetCampaignInt(sDB, "2_ADV_GOODAXIS"); if (nAlignment == ALIGNMENT_GOOD) sEvent = "good"; //some story detailing the adventurer's heroic deeds else if (nAlignment == ALIGNMENT_EVIL) sEvent = "evil"; //some story detailing the adventurer's evil deeds else if (nAlignment == ALIGNMENT_NEUTRAL) sEvent = "neutral"; //some story detailing the adventurer's deeds that might be seen as evil or good sEvent = IntToString(Random(2)+1) + sEvent; //two variants for each: 1good - rescued a merchant from bandits, 2good - took no reward for fending off an assault on a village, 1evil - used a passerby as a living shield in combat, 2evil - murdered a merchant, 1neutral - 1good, 2neutral - 1evil if (sEvent == "1neutral") sEvent = "1good"; if (sEvent == "2neutral") sEvent = "1evil"; sEventData1 = GetCampaignString(sDB, "2_ADV_NAME") + " " + GetCampaignString(sDB, "2_ADV_LASTNAME"); } if (sEvent == "famous3") { nAlignment = GetCampaignInt(sDB, "3_ADV_GOODAXIS"); if (nAlignment == ALIGNMENT_GOOD) sEvent = "good"; //some story detailing the adventurer's heroic deeds else if (nAlignment == ALIGNMENT_EVIL) sEvent = "evil"; //some story detailing the adventurer's evil deeds else if (nAlignment == ALIGNMENT_NEUTRAL) sEvent = "neutral"; //some story detailing the adventurer's deeds that might be seen as evil or good sEvent = IntToString(Random(2)+1) + sEvent; //two variants for each: 1good - rescued a merchant from bandits, 2good - took no reward for fending off an assault on a village, 1evil - used a passerby as a living shield in combat, 2evil - murdered a merchant, 1neutral - 1good, 2neutral - 1evil if (sEvent == "1neutral") sEvent = "1good"; if (sEvent == "2neutral") sEvent = "1evil"; sEventData1 = GetCampaignString(sDB, "3_ADV_NAME") + " " + GetCampaignString(sDB, "3_ADV_LASTNAME"); } //Let's save everything in the DB int nSlot = OldestRumorSlot(); string sSlot = IntToString(nSlot); SetCampaignString(sDB, sSlot+"_EVENT", sEvent); if (sEventData1 != "") SetCampaignString(sDB, sSlot+"_EV_DAT1", sEventData1); } if (nEvent == 2) //invisible event { switch (Random(4)) { case 0: sEvent = "robbery"; break; //a town's caravan has been assaulted: -1 to local economy case 1: sEvent = "export"; break; //demand for exported goods has increased: +1 to local economy case 2: sEvent = "strike"; break; //there have been some strikes lately: -1 to local economy case 3: sEvent = "treasure"; break; //the militia has found some treasure near the town: +1 to local economy } int nBonus; if (sEvent == "robbery" || sEvent == "strike") nBonus = -1; else nBonus = 1; int nEconomy = GetCampaignInt(sDB, sTown+"_ECONOMY"); if (nEconomy+nBonus > 9) SetCampaignInt(sDB, sTown+"_ECONOMY", 9); else if (nEconomy+nBonus < 1) SetCampaignInt(sDB, sTown+"_ECONOMY", 1); else SetCampaignInt(sDB, sTown+"_ECONOMY", nEconomy+nBonus); //Let's save everything in the DB int nSlot = OldestRumorSlot(); string sSlot = IntToString(nSlot); SetCampaignString(sDB, sSlot+"_EVENT", sEvent); SetCampaignString(sDB, sSlot+"_EV_DAT1", sTown); } if (nEvent == 3) //adventurer's death or arrival { string sEventData1; int nAdventurer; string sAdventurer; //adventurer's death if there are three adventurers already if (GetCampaignInt(sDB, "1_ADV_OCCUPIED") == TRUE && GetCampaignInt(sDB, "2_ADV_OCCUPIED") == TRUE && GetCampaignInt(sDB, "3_ADV_OCCUPIED") == TRUE) { nAdventurer = Random(3)+1; //randomly choose 1 of the adventurers to die sAdventurer = IntToString(nAdventurer); sEvent = IntToString(Random(5)+1)+"death"; //1 - killed, 2 - killed in a dungeon, 3 - disappeared with no trace, 4 - went to explore the north, 5 - left the realm sEventData1 = GetCampaignString(sDB, sAdventurer+"_ADV_NAME") + " " + GetCampaignString(sDB, sAdventurer+"_ADV_LASTNAME"); DeleteAdventurerNPC(nAdventurer); } else //adventurer's arrival if there is a free slot { nAdventurer; if (GetCampaignInt(sDB, "1_ADV_OCCUPIED") == FALSE) nAdventurer = 1; else if (GetCampaignInt(sDB, "2_ADV_OCCUPIED") == FALSE) nAdventurer = 2; else if (GetCampaignInt(sDB, "3_ADV_OCCUPIED") == FALSE) nAdventurer = 3; sAdventurer = IntToString(nAdventurer); sEvent = "arrival"; GenerateAdventurerNPC(nAdventurer); sEventData1 = GetCampaignString(sDB, sAdventurer+"_ADV_NAME") + " " + GetCampaignString(sDB, sAdventurer+"_ADV_LASTNAME"); } //Let's save everything in the DB int nSlot = OldestRumorSlot(); string sSlot = IntToString(nSlot); SetCampaignString(sDB, sSlot+"_EVENT", sEvent); SetCampaignString(sDB, sSlot+"_EV_DAT1", sEventData1); } if (nEvent == 4) //special event { string sEventData1; switch (Random(3)) { case 0: sEvent = "special"; break; //special event (Alverton - jousting tournament) case 1: sEvent = "invasion"; break; //monster invasion case 2: sEvent = "festival"; break; //town festival } sEventData1 = sTown; if (sEvent == "special") SpawnSpecial(sTown); if (sEvent == "invasion") SpawnInvasion(sTown, nHighestLvl); if (sEvent == "festival") SpawnFestival(sTown); //Let's save everything in the DB int nSlot = OldestRumorSlot(); string sSlot = IntToString(nSlot); SetCampaignString(sDB, sSlot+"_EVENT", sEvent); SetCampaignString(sDB, sSlot+"_EV_DAT1", sEventData1); } }