bondage-college-mirr/BondageClub/Screens/Room/Pandora/Pandora.js
Jean-Baptiste Emmanuel Zorg 347644356d Move all the access list checks into a single function
This adds nicer methods to the Character object for checking someone
against the various player/character access lists.
2025-03-29 17:42:56 +01:00

1714 lines
69 KiB
JavaScript

"use strict";
var PandoraBackground = "Pandora/Ground/Entrance";
/** @type {PandoraBaseRoom} */
var PandoraCurrentRoom = null;
/** @type {PandoraBaseRoom} */
var PandoraPreviousRoom = null;
/** @type {PandoraBaseRoom[]} */
var PandoraRoom = [];
/** @type {PandoraFloorDirection[]} */
var PandoraDirectionList = ["South", "North", "East", "West"];
/** @type {PandoraFloorDirection[]} */
var PandoraDirectionListFrom = ["North", "South", "West", "East"];
/** @type {""|"Search"|"Rest"|"Paint"} */
var PandoraMode = "";
var PandoraModeTimer = 0;
var PandoraModeAppearance = null;
var PandoraMessage = null;
/** @type {NPCCharacter[]} */
var PandoraParty = [];
/** @type {null | NPCCharacter} */
var PandoraFightCharacter = null;
var PandoraRandomNPCList = ["MemberNew", "MemberOld", "Cosplay", "Mistress", "Slave", "Maid", "Guard"];
var PandoraMoveDirectionTimer = { Direction: "", Timer: 0 };
/** @type {null | PandoraBaseRoom} */
var PandoraTargetRoom = null;
var PandoraClothes = "Random";
var PandoraWillpower = 20;
var PandoraMaxWillpower = 20;
var PandoraMoney = 0;
var PandoraTimer = 0;
var PandoraChestCount = 0;
var PandoraPaint = false;
var PandoraReverseMaidDone = 0;
var PandoraReverseMaidTotal = 0;
var PandoraPenitentiaryCreateTimer = 0;
var PandoraPenitentiaryActivityList = ["Bondage", "Cage", "Chastity", "Detain", "Gag", "Hogtie", "Hose", "Lock", "Naked", "Spank", "Ungag", "Release", "Shibari", "Uncage", "Unchaste", "Unlock"];
var PandoraPenitentiaryActivityTimer = 0;
var PandoraPenitentiarySafewordRooms = [];
var PandoraPenitentiaryCageList = ["WoodenBox", "SmallWoodenBox", "Cage", "LowCage", "PersonalCage", "TheDisplayFrame", "TheHangingFrame", "WoodenBoxOpenHead", "SmallWoodenBoxOpenHead", "Kennel", "TransportWoodenBox"];
var PandoraPenitentiaryStartNewRoom = false;
/**
* NPC Dialog functions
* @returns {boolean} - TRUE if the dialog option will be available to the player
*/
function PandoraCanStartRecruit() { return (((CurrentCharacter.Recruit == null) || (CurrentCharacter.Recruit == 0)) && (!CurrentCharacter.IsNaked() || CurrentCharacter.Archetype == "Slave")); }
function PandoraCanRecruit() { return (CurrentCharacter.Recruit + (InfiltrationPerksActive("Recruiter") ? 0.25 : 0) + ((CurrentCharacter.Archetype === "Slave" && PandoraClothes == "Mistress") ? 0.25 : 0) >= CurrentCharacter.RecruitOdds); }
function PandoraCharacterCanJoin() { return ((PandoraParty.length == 0) || (PandoraParty[0].Name != CurrentCharacter.Name)); }
function PandoraCharacterCanLeave() { return ((PandoraParty.length == 1) && (PandoraParty[0].Name == CurrentCharacter.Name) && ((PandoraCurrentRoom.Character == null) || (PandoraCurrentRoom.Character.length <= 1))); }
function PandoraOdds75() { return ((CurrentCharacter.RandomOdds == null) || (CurrentCharacter.RandomOdds > 0.25)); }
function PandoraOdds50() { return ((CurrentCharacter.RandomOdds == null) || (CurrentCharacter.RandomOdds > 0.50)); }
function PandoraOdds25() { return ((CurrentCharacter.RandomOdds == null) || (CurrentCharacter.RandomOdds > 0.75)); }
/** @param {string} Costume */
function PandoraCostumeIs(Costume) { return (PandoraClothes == Costume); }
/** @param {number} Number */
function PandoraQuizIs(Number) { return ((CurrentCharacter.QuizLog != null) && (CurrentCharacter.QuizLog[CurrentCharacter.QuizLog.length - 1].toString() == Number.toString())); }
function PandoraCanAskForPaint() { return (!PandoraPaint && (PandoraClothes == "Maid") && (InfiltrationMission == "ReverseMaid")); }
/**
* Loads the Pandora's Box screen
* @returns {void} - Nothing
*/
function PandoraLoad() {
}
/**
* Returns the color of the direction buttons, it can change if the direction was recently navigated to
* The Cartographer perk can show the returning path in yellow
* @param {"North" | "South" | "East" | "West"} Direction - The cardinal direction
* @returns {string} - The color to use
*/
function PandoraDirectionButtonColor(Direction) {
if ((PandoraMoveDirectionTimer.Timer >= CommonTime()) && (PandoraMoveDirectionTimer.Direction === Direction))
return PandoraDirectionAvailable(Direction) ? (((Direction == PandoraCurrentRoom.DirectionMap[0]) && InfiltrationPerksActive("Cartographer") && (PandoraCurrentRoom.Background != "Entrance")) ? "#BFFF40" : "#80FF80") : "#408040";
else
return PandoraDirectionAvailable(Direction) ? (((Direction == PandoraCurrentRoom.DirectionMap[0]) && InfiltrationPerksActive("Cartographer") && (PandoraCurrentRoom.Background != "Entrance")) ? "#FFFF00" : "White") : "#BF8080";
}
/**
* Returns whether the player can move in the specified direction
* @param {"North" | "South" | "East" | "West"} Direction - The cardinal direction to check
* @returns {boolean} - Whether the direction can be accessed
*/
function PandoraDirectionAvailable(Direction) {
return (PandoraCurrentRoom.DirectionMap.indexOf(Direction) >= 0);
}
/**
* Draws the Pandora text data
* @param {Number} Y - The Y position
* @returns {boolean} - Whether the direction can be accessed
*/
function PandoraDrawData(Y) {
if (InfiltrationMission == "CatBurglar") {
DrawText("$" + PandoraMoney.toString() + (InfiltrationPerksActive("Detector") ? " (" + PandoraChestCount.toString() + ")" : ""), 1887, Y, "White", "Black");
DrawText(TimerToString(PandoraTimer - CommonTime()), 1887, Y + 55, "White", "Black");
return;
}
if (InfiltrationMission == "ReverseMaid") {
DrawText("$" + PandoraMoney.toString() + (InfiltrationPerksActive("Detector") ? " (" + PandoraChestCount.toString() + ")" : ""), 1887, Y, "White", "Black");
DrawText(TextGet("ReverseMaidProgress") + " " + Math.round(PandoraReverseMaidDone / PandoraReverseMaidTotal * 100).toString() + "%", 1887, Y + 55, "White", "Black");
return;
}
DrawText("$" + PandoraMoney.toString() + (InfiltrationPerksActive("Detector") ? " (" + PandoraChestCount.toString() + ")" : ""), 1887, Y + 55, "White", "Black");
}
/**
* Runs and draws all Pandora's Box screens
* @returns {void} - Nothing
*/
function PandoraRun() {
// If time is up for catburglar mission
if ((PandoraTimer < CommonTime()) && (InfiltrationMission == "CatBurglar"))
return PandoraPunishmentIntro(false, TextGet("CatBurglarPunishment"));
// Gets the current room & background
if ((PandoraRoom.length == 0) || (PandoraCurrentRoom == null)) return;
PandoraBackground = "Pandora/" + PandoraCurrentRoom.Floor + "/" + PandoraCurrentRoom.Background;
if (PandoraCurrentRoom.Graffiti != null) DrawImage("Screens/Room/Pandora/Graffiti" + PandoraCurrentRoom.Graffiti.toString() + ".png", 0, 0);
if (InfiltrationMission == "CatBurglar") DrawRect(0, 0, 2000, 1000, "#00000080");
// The search square are drawn even out of search mode
if (PandoraCurrentRoom.SearchSquare != null)
for (let S = 0; S < PandoraCurrentRoom.SearchSquare.length; S++)
DrawRect(PandoraCurrentRoom.SearchSquare[S].X, PandoraCurrentRoom.SearchSquare[S].Y, PandoraCurrentRoom.SearchSquare[S].W, PandoraCurrentRoom.SearchSquare[S].H, "#00FFFF7F");
// In search mode, we draw all previous searches done in that area
if (PandoraMode == "Search") {
DrawButton(1885, 885, 90, 90, "", "White", "Icons/Search.png", TextGet("SearchStop"));
return;
}
// In paint mode, we offer many graffities for the player to pick
if (PandoraMode == "Paint") {
if (PandoraCurrentRoom.Graffiti == null) {
for (let G = 0; G <= 7; G++)
DrawButton(1885, 25 + (G * 115), 90, 90, "", "White", "Icons/Paint.png", TextGet("Graffiti" + G.toString()));
} else DrawButton(1885, 25, 90, 90, "", "White", "Icons/Paint.png", TextGet("AlreadyPainted"));
return;
}
// In rest mode, the timer will tick slowly and heal the player for 1 willpower
if (PandoraMode == "Rest") {
DrawButton(1885, 25, 90, 90, "", "White", "Icons/Rest.png", TextGet("RestStop"));
DrawProgressBar(1785, 954, 205, 36, Math.round(PandoraWillpower / PandoraMaxWillpower * 100));
DrawText(PandoraWillpower.toString(), 1888, 973, "black", "white");
DrawCharacter(Player, 750, 0, 1);
if (PandoraModeTimer < CommonTime()) {
PandoraWillpower = PandoraWillpower + 1;
if (PandoraWillpower > PandoraMaxWillpower) PandoraWillpower = PandoraMaxWillpower;
PandoraModeTimer = CommonTime() + ((InfiltrationPerksActive("Recovery")) ? 10000 : 12000);
}
return;
}
// Draws up to 4 characters in the room, including the player
let Pos = 690 - (PandoraCurrentRoom.Character.length + PandoraParty.length) * 230;
DrawCharacter(Player, Pos, 0, 1);
let AllowMove = true;
for (let C = 0; C < PandoraParty.length; C++)
DrawCharacter(PandoraParty[C], Pos + ((C + 1) * 460), 0, 1);
for (let C = 0; C < PandoraCurrentRoom.Character.length; C++) {
DrawCharacter(PandoraCurrentRoom.Character[C], Pos + ((C + 1 + PandoraParty.length) * 460), 0, 1);
if ((PandoraCurrentRoom.Character[C].AllowMove != null) && (PandoraCurrentRoom.Character[C].AllowMove == false)) AllowMove = false;
}
// If we allow moving, we draw the paths
PandoraDrawData(AllowMove ? 525 : 860);
if (AllowMove) {
for (let P = 0; P < PandoraCurrentRoom.Path.length; P++)
DrawButton(1885, 25 + P * 115, 90, 90, "", "White", "Icons/" + PandoraCurrentRoom.Direction[P] + ".png", TextGet("Path" + PandoraCurrentRoom.Direction[P]));
DrawButton(1842, 620, 90, 90, "", PandoraDirectionButtonColor("North"), "Icons/North.png", TextGet("DirectionNorth"), !PandoraDirectionAvailable("North"));
DrawButton(1785, 735, 90, 90, "", PandoraDirectionButtonColor("West"), "Icons/West.png", TextGet("DirectionWest"), !PandoraDirectionAvailable("West"));
DrawButton(1842, 850, 90, 90, "", PandoraDirectionButtonColor("South"), "Icons/South.png", TextGet("DirectionSouth"), !PandoraDirectionAvailable("South"));
DrawButton(1900, 735, 90, 90, "", PandoraDirectionButtonColor("East"), "Icons/East.png", TextGet("DirectionEast"), !PandoraDirectionAvailable("East"));
}
// If we must draw a message in the middle of the screen
if ((PandoraMessage != null) && (PandoraMessage.Timer != null) && (PandoraMessage.Text != null) && (PandoraMessage.Timer >= CommonTime())) {
DrawRect(450, 465, 1100, 70, "black");
DrawRect(452, 467, 1096, 66, "white");
DrawTextWrap(PandoraMessage.Text, 450, 465, 1100, 70, "black");
}
// Draw the willpower / max
DrawProgressBar(1785, 954, 205, 36, Math.round(PandoraWillpower / PandoraMaxWillpower * 100));
DrawText(PandoraWillpower.toString(), 1888, 973, "black", "white");
}
/**
* Enters a new mode for the Pandora screen, such as rest or search mode
* @param {PandoraMode} NewMode
* @returns {void} - Nothing
*/
function PandoraSetMode(NewMode) {
// Exit from a previous mode if needed
if ((PandoraMode == "Rest") && (NewMode == "") && (PandoraModeAppearance != null)) {
Player.Appearance = PandoraModeAppearance.slice(0);
PandoraModeAppearance = null;
CharacterRefresh(Player, true);
}
// Enters the need mode
PandoraMode = NewMode;
// In rest, the player switch to a towel and heals overtime
if (NewMode == "Rest") {
PandoraModeAppearance = Player.Appearance.slice(0);
CharacterNaked(Player);
InventoryWear(Player, "BodyTowel1", "Cloth");
InventoryWear(Player, "HeadTowel1", "Hat");
PandoraModeTimer = CommonTime() + ((InfiltrationPerksActive("Recovery")) ? 10000 : 12000);
}
// Cannot enter search mode if the item is already found
if ((NewMode == "Search") && (InfiltrationTarget.Found === true)) {
PandoraMsgBox(TextGet("AlreadyFound").replace("TargetName", InfiltrationTarget.Name));
PandoraMode = "";
}
// Cannot enter paint mode without paint
if ((NewMode == "Paint") && !PandoraPaint) {
PandoraMsgBox(TextGet("NoPaint"));
PandoraMode = "";
}
// Cannot enter paint mode if the room isn't empty
if ((NewMode == "Paint") && (PandoraCurrentRoom.Character.length >= 1)) {
PandoraMsgBox(TextGet("RoomEmptyToPaint"));
PandoraMode = "";
}
}
/**
* Handles clicks in all Pandora's screen
* @returns {void} - Nothing
*/
function PandoraClick() {
// Checks if we clicked on the player
if (PandoraRoom.length == 0) return;
// If there's a message running, we do not allow any clicks
if ((PandoraMessage != null) && (PandoraMessage.Timer != null) && (PandoraMessage.Text != null) && (PandoraMessage.Timer >= CommonTime()))
return;
// In search mode, we can click anywhere on the screen
if (PandoraMode == "Search") {
if (MouseIn(0, 0, 1850, 1000)) {
if (PandoraCurrentRoom.SearchSquare == null) PandoraCurrentRoom.SearchSquare = [];
let Radius = InfiltrationPerksActive("Investigation") ? 250 : 175;
PandoraCurrentRoom.SearchSquare.push({ X: MouseX - Radius, Y: MouseY - Radius, W: Radius * 2, H: Radius * 2 });
if ((PandoraCurrentRoom.ItemX != null) && (PandoraCurrentRoom.ItemY != null) && MouseIn(PandoraCurrentRoom.ItemX - Radius, PandoraCurrentRoom.ItemY - Radius, Radius * 2, Radius * 2)) {
InfiltrationTarget.Found = true;
PandoraSetMode("");
PandoraMsgBox(TextGet("FoundItem").replace("TargetName", InfiltrationTarget.Name));
}
}
if (MouseIn(1885, 885, 90, 90)) PandoraSetMode("");
return;
}
// In rest mode, the player can exit it and stop healing
if (PandoraMode == "Rest") {
if (MouseIn(1885, 25, 90, 90)) PandoraSetMode("");
return;
}
// In paint mode, the player puts a graffiti on the background
if (PandoraMode == "Paint") {
if (PandoraCurrentRoom.Graffiti == null) {
for (let G = 0; G <= 7; G++)
if (MouseIn(1885, 25 + (G * 115), 90, 90)) {
PandoraReverseMaidDone++;
PandoraCurrentRoom.Graffiti = G;
break;
}
} else if (MouseIn(1885, 25, 90, 90)) PandoraSetMode("");
return;
}
// Checks if we clicked on a character
let Pos = 690 - (PandoraCurrentRoom.Character.length + PandoraParty.length) * 230;
if (MouseIn(Pos, 0, 500, 1000)) return CharacterSetCurrent(Player);
let AllowMove = true;
for (let C = 0; C < PandoraCurrentRoom.Character.length; C++) {
if (MouseIn(Pos + ((C + 1 + PandoraParty.length) * 460), 0, 500, 1000)) return CharacterSetCurrent(PandoraCurrentRoom.Character[C]);
if ((PandoraCurrentRoom.Character[C].AllowMove != null) && (PandoraCurrentRoom.Character[C].AllowMove == false)) AllowMove = false;
}
// If we allow moving, we can switch room, rooms that start with "Rest" or "Search" have special modes
if (AllowMove) {
for (let C = 0; C < PandoraParty.length; C++)
if (MouseIn(Pos + ((C + 1) * 460), 0, 500, 1000))
return CharacterSetCurrent(PandoraParty[C]);
for (let P = 0; P < PandoraCurrentRoom.Path.length; P++)
if (MouseIn(1900, 25 + P * 115, 90, 90)) {
const Floor = PandoraCurrentRoom.Path[P].Floor;
if (Floor == "Exit")
return CommonSetScreen("Room", "Infiltration");
if ((Floor == "Search") || (Floor == "Paint") || (Floor == "Rest"))
return PandoraSetMode(Floor);
return PandoraEnterRoom(/** @type {PandoraBaseRoom} */(PandoraCurrentRoom.Path[P]));
}
if (MouseIn(1842, 620, 90, 90) && (PandoraDirectionAvailable("North"))) return PandoraEnterRoom(PandoraCurrentRoom.PathMap[PandoraCurrentRoom.DirectionMap.indexOf("North")], "North");
if (MouseIn(1785, 735, 90, 90) && (PandoraDirectionAvailable("West"))) return PandoraEnterRoom(PandoraCurrentRoom.PathMap[PandoraCurrentRoom.DirectionMap.indexOf("West")], "West");
if (MouseIn(1842, 850, 90, 90) && (PandoraDirectionAvailable("South"))) return PandoraEnterRoom(PandoraCurrentRoom.PathMap[PandoraCurrentRoom.DirectionMap.indexOf("South")], "South");
if (MouseIn(1900, 735, 90, 90) && (PandoraDirectionAvailable("East"))) return PandoraEnterRoom(PandoraCurrentRoom.PathMap[PandoraCurrentRoom.DirectionMap.indexOf("East")], "East");
}
}
/**
* Handles the key pressed in Pandora's Box, allow WASD to move around
* @param {KeyboardEvent} event - Key event
* @returns {boolean} - Whether the supplied event was understood/used.
*/
function PandoraKeyDown (event) {
/** @type {"North" | "South" | "East" | "West" | undefined} */
let dir;
const movingAllowed = PandoraCurrentRoom.Character.every (i => i.AllowMove == null || i.AllowMove);
if (movingAllowed && PandoraMode == "" && !CommonKey.GetModifiers(event)) {
switch (CommonKeyMove (event, true)) {
case 'u':
dir = "North";
break;
case 'l':
dir = "West";
break;
case 'd':
dir = "South";
break;
case 'r':
dir = "East";
// fallthrough
default:
break;
}
if (dir !== undefined && PandoraDirectionAvailable (dir)) {
PandoraEnterRoom (PandoraCurrentRoom.PathMap[PandoraCurrentRoom.DirectionMap.indexOf (dir)], dir);
}
}
return dir !== undefined;
}
/**
* Prepares a text popup for Pandora's Box
* @param {string} Text
* @returns {void} - Nothing
*/
function PandoraMsgBox(Text) {
PandoraMessage = { Timer: CommonTime() + 3000, Text: Text };
}
/**
* Generates a random NPC for Pandora's Box missions, clear the cache if it was generated before
* @param {string} Group - The main group for that NPC (Random, Entrance, Underground)
* @param {NPCArchetype} Archetype - The NPC function within Pandora's (Guard, Mistress, Slave, Maid, etc.)
* @param {string} Name - The name to give to that NPC, can be RANDOM for a fully random name
* @param {boolean} AllowItem - TRUE if we allow using items on her by default
* @returns {NPCCharacter} - The NPC character to return
*/
function PandoraGenerateNPC(Group, Archetype, Name, AllowItem) {
const oldNPC = Character.find(c => c.CharacterID === "NPC_Pandora_" + Group + Archetype);
if (oldNPC) {
CharacterDelete(oldNPC);
}
let NPC = CharacterLoadNPC("NPC_Pandora_" + Group + Archetype);
if (Name == "RANDOM")
NPC.Name = CharacterGenerateRandomName();
else
NPC.Name = Name;
CharacterRelease(NPC);
NPC.Archetype = Archetype;
NPC.Stage = "0";
NPC.AllowItem = AllowItem;
NPC.AllowMove = false;
NPC.Recruit = 0;
NPC.RecruitOdds = (Archetype === "Slave") ? 1 : 0.75;
PandoraDress(NPC, NPC.Archetype);
return NPC;
}
/**
* Dress a character in the Rival Club fashion
* @param {NPCCharacter} C
* @param {string} Type
* @returns {void} - Nothing
*/
function PandoraDress(C, Type) {
// The maids have a red outfit
if (Type == "Maid") {
InventoryRemove(C, "Gloves");
InventoryRemove(C, "ClothAccessory");
InventoryWear(C, "MaidOutfit2", "Cloth", "#804040");
InventoryWear(C, "MaidHairband1", "Hat", "#804040");
if (InventoryGet(C, "Socks") == null) InventoryWear(C, "Socks3", "Socks", "#804040");
else InventoryGet(C, "Socks").Color = "#804040";
if (InventoryGet(C, "Bra") == null) InventoryWear(C, "Bra1", "Bra", "#222222");
else InventoryGet(C, "Bra").Color = "#222222";
if (InventoryGet(C, "Panties") == null) InventoryWear(C, "Panties1", "Panties", "#222222");
else InventoryGet(C, "Panties").Color = "#222222";
if (InventoryGet(C, "Shoes") == null) InventoryWear(C, "Shoes1", "Shoes", "#222222");
else InventoryGet(C, "Shoes").Color = "#222222";
if (!C.IsPlayer()) InventoryWear(C, "MaidCollar", "ItemNeck", "#804040");
CharacterRefresh(C, false);
return;
}
// The guards are wearing a police hat and latex
if (Type == "Guard") {
InventoryRemove(C, "Gloves");
InventoryRemove(C, "ClothAccessory");
InventoryWear(C, "PoliceWomanHat", "Hat", "Default");
InventoryWear(C, "CorsetShirt", "Cloth", "Default");
InventoryWear(C, "LatexPants1", "ClothLower", "Default");
InventoryWear(C, "DeluxeBoots", "Shoes", "#222222");
InventoryWear(C, "LatexSocks1", "Shoes", "#222222");
CharacterRefresh(C, false);
return;
}
// The guards are wearing a police hat and latex
if (Type == "Slave") {
CharacterNaked(C);
InventoryWear(C, "StrictPostureCollar", "ItemNeck", "#FFD700");
InventoryLock(C, "ItemNeck", "PandoraPadlock", -1);
InventoryWear(C, "MetalChastityBelt", "ItemPelvis", "#FFD700");
InventoryLock(C, "ItemPelvis", "PandoraPadlock", -1);
CharacterRefresh(C, false);
return;
}
// The Mistress wear gold uniforms
if (Type == "Mistress") {
InventoryRemove(C, "ClothAccessory");
InventoryWear(C, "MistressGloves", "Gloves", "#FFD700");
InventoryWear(C, "MistressBoots", "Shoes", "#FFD700");
InventoryWear(C, "MistressTop", "Cloth", "#FFD700");
InventoryWear(C, "MistressBottom", "ClothLower", "#FFD700");
CharacterRefresh(C, false);
return;
}
// Cosplay wear random costumes
if (Type == "Cosplay") {
CharacterNaked(C);
InventoryWearRandom(C, "Bra");
InventoryWearRandom(C, "Panties");
if (Math.random() > 0.33) InventoryWearRandom(C, "Wings");
if (Math.random() > 0.33) InventoryWearRandom(C, "HairAccessory1");
if (Math.random() > 0.33) InventoryWearRandom(C, "TailStraps");
if (Math.random() > 0.5) InventoryWearRandom(C, "HairAccessory2");
if (Math.random() > 0.5) InventoryWearRandom(C, "Corset");
if (Math.random() > 0.5) InventoryWearRandom(C, "Mask");
if (Math.random() > 0.666) InventoryWearRandom(C, "Garters");
if (Math.random() > 0.666) InventoryWearRandom(C, "Socks");
if (Math.random() > 0.666) InventoryWearRandom(C, "Gloves");
if (Math.random() > 0.666) InventoryWearRandom(C, "Bracelet");
return;
}
// Since no defined type is found, we fully randomize the clothes and appearance
CharacterAppearanceFullRandom(C);
CharacterRefresh(C, false);
}
/**
* When the players enters a new room, we keep the previous room
* @param {PandoraBaseRoom} Room - The room to step into
* @param {PandoraDirection | ""} [Direction] - The direction the player comes from
* @returns {void} - Nothing
*/
function PandoraEnterRoom(Room, Direction) {
// Removes 15 seconds from the clock
PandoraTimer = PandoraTimer - 15000;
// Shoes the incoming direction for a little while
if ((Direction != null) && (Direction != "")) PandoraMoveDirectionTimer = { Direction: Direction, Timer: CommonTime() + 1500 };
// Sets the new room and keep the previous
PandoraPreviousRoom = PandoraCurrentRoom;
PandoraCurrentRoom = Room;
// 33% odds of removing a previous random NPC if she can walk
if (PandoraCurrentRoom.Character.length >= 1) {
let Char = PandoraCurrentRoom.Character[0];
if ((Char.AccountName.indexOf("NPC_Pandora_Random") == 0) && (Math.random() > 0.667) && Char.CanWalk()) {
PandoraCurrentRoom.Character = [];
CharacterDelete(Char);
return;
}
}
// 4% odds of spawning a new random NPC in the room
if ((PandoraCurrentRoom.Background.indexOf("Entrance") < 0) && (PandoraCurrentRoom.Character.length == 0) && (Math.random() > 0.96) && (InfiltrationMission != "CatBurglar")) {
let Type = /** @type {NPCArchetype} */ (CommonRandomItemFromList("", PandoraRandomNPCList));
let Char = PandoraGenerateNPC("Random", Type, "RANDOM", (Type === "Slave"));
Room.Character.push(Char);
}
// 1% odds of spawning a night guard NPC in the room
if ((PandoraCurrentRoom.Background.indexOf("Entrance") < 0) && (PandoraCurrentRoom.Character.length == 0) && (Math.random() > 0.99) && (InfiltrationMission == "CatBurglar")) {
let Char = PandoraGenerateNPC("Night", "Guard", "RANDOM", false);
Room.Character.push(Char);
}
// If we enter a room with a maid that's not bound, she can intercept the player if the mission is almost completed
if ((PandoraCurrentRoom.Character.length == 1) && (PandoraCurrentRoom.Character[0].AccountName.indexOf("Maid") >= 0) && PandoraCurrentRoom.Character[0].CanInteract()) {
let StartDialog = "";
if ((InfiltrationMission == "Retrieve") && (InfiltrationTarget.Found)) StartDialog = InfiltrationMission + ((PandoraClothes == "Maid") ? "Maid" : "Random") + "0";
if ((InfiltrationMission == "Rescue") && (PandoraParty.length == 1) && (PandoraParty[0].Name == InfiltrationTarget.Name)) StartDialog = InfiltrationMission + ((PandoraClothes == "Guard") ? "Guard" : "Random") + "0";
if ((InfiltrationMission == "Kidnap") && (PandoraParty.length == 1) && (PandoraParty[0].Name == InfiltrationTarget.Name) && PandoraParty[0].CanTalk()) StartDialog = InfiltrationMission + "Random" + "0";
if ((PandoraCurrentRoom.Character[0].AccountName.indexOf("EntranceMaid") >= 0) && (PandoraParty.length == 1) && (PandoraParty[0].AccountName.indexOf("RandomMistress") >= 0) && (InventoryGet(PandoraParty[0], "ItemHood") == null)) StartDialog = "MistressArrest0";
if (StartDialog != "") {
CharacterRelease(PandoraCurrentRoom.Character[0]);
PandoraCurrentRoom.Character[0].RandomOdds = Math.random() + 0.2 - (InfiltrationDifficulty * 0.1);
PandoraCurrentRoom.Character[0].AllowMove = false;
PandoraCurrentRoom.Character[0].Stage = StartDialog;
}
}
// If we enter a room with a guard that's not bound, she can intercept the player
if ((PandoraCurrentRoom.Character.length == 1) && (PandoraCurrentRoom.Character[0].AccountName.indexOf("RandomGuard") >= 0) && PandoraCurrentRoom.Character[0].CanInteract()) {
let ArrestDialog = "";
if (!PandoraCurrentRoom.Character[0].AllowMove && (SkillGetLevel(Player, "Infiltration") >= Math.floor(Math.random() * 10))) ArrestDialog = "InfiltrationArrest";
if ((InfiltrationMission == "Kidnap") && (PandoraParty.length == 1) && (PandoraParty[0].Name == InfiltrationTarget.Name) && PandoraParty[0].CanTalk()) ArrestDialog = "KidnapArrest";
if ((PandoraParty.length == 1) && (PandoraParty[0].AccountName.indexOf("RandomGuard") >= 0)) ArrestDialog = "GuardArrest";
if ((PandoraParty.length == 1) && (PandoraParty[0].AccountName.indexOf("RandomMistress") >= 0) && PandoraParty[0].CanTalk()) ArrestDialog = "MistressArrest";
if (ArrestDialog != "") {
CharacterRelease(PandoraCurrentRoom.Character[0]);
PandoraCurrentRoom.Character[0].AllowMove = false;
PandoraCurrentRoom.Character[0].Stage = ArrestDialog;
}
}
// If we enter a room with a Dominatrix that's not bound, she can intercept and challenge the player
if ((PandoraCurrentRoom.Character.length == 1) && (PandoraCurrentRoom.Character[0].AccountName.indexOf("RandomMistress") >= 0) && PandoraCurrentRoom.Character[0].CanInteract()) {
let MistressDialog = "";
if (!PandoraCurrentRoom.Character[0].AllowMove && (SkillGetLevel(Player, "Infiltration") >= Math.floor(Math.random() * 10))) MistressDialog = "50";
if (PandoraClothes == "Mistress") MistressDialog = "60";
if ((PandoraParty.length == 1) && (PandoraParty[0].AccountName.indexOf("RandomMistress") >= 0)) MistressDialog = "70";
if (MistressDialog != "") {
CharacterRelease(PandoraCurrentRoom.Character[0]);
PandoraCurrentRoom.Character[0].RandomOdds = Math.random() - (SkillGetLevel(Player, "Infiltration") / 10);
PandoraCurrentRoom.Character[0].AllowMove = false;
PandoraCurrentRoom.Character[0].Stage = MistressDialog;
}
}
}
/**
* Generates a chest randomly in the room cell (35% odds in cat burglar missions, 5% in other missions)
* @param {object} Room - The room in which the chest could be placed
* @returns {void} - Nothing
*/
function PandoraCreateChest(Room) {
if (Room.Background.indexOf("Cell") != 0) return;
if ((PandoraChestCount == 0) && (Math.random() > ((InfiltrationMission == "CatBurglar") ? 0.50 : 0.1))) return;
if ((PandoraChestCount >= 1) && (PandoraChestCount <= 3) && (Math.random() > ((InfiltrationMission == "CatBurglar") ? 0.33 : 0.05))) return;
if ((PandoraChestCount >= 4) && (PandoraChestCount <= 6) && (Math.random() > ((InfiltrationMission == "CatBurglar") ? 0.20 : 0.03))) return;
if ((PandoraChestCount >= 7) && (PandoraChestCount <= 10) && (Math.random() > ((InfiltrationMission == "CatBurglar") ? 0.13 : 0.02))) return;
if ((PandoraChestCount >= 11) && (Math.random() > ((InfiltrationMission == "CatBurglar") ? 0.05 : 0.01))) return;
PandoraChestCount++;
let Char = PandoraGenerateNPC("Treasure", "Chest", TextGet("Chest"), false);
Char.FixedImage = "Screens/Room/Pandora/Chest" + Math.floor(Math.random() * 3).toString() + ".png";
Char.AllowMove = true;
Room.Character.push(Char);
}
/**
* Generates random rooms linked on the entry room
* @param {PandoraBaseRoom} EntryRoom - The room object that's leading to that floor
* @param {PandoraFloorDirection} DirectionFrom - The entry direction
* @param {number} RoomLevel - The room level, the higher it goes, the higher the chances it will be a dead-end
* @param {number} MaxRoom - The max number of rooms to generate.
* @returns {void} - Nothing
*/
function PandoraGenerateRoom(EntryRoom, DirectionFrom, RoomLevel, MaxRoom) {
// Over the max number of rooms, the dungeon layout is always invalid
if (PandoraRoom.length >= MaxRoom) return;
// The higher the room level, the less paths there will be
let PathCount = 0;
if (EntryRoom.Background.indexOf("Entrance") == 0) PathCount = 2 + Math.floor(Math.random() * 3);
if (EntryRoom.Background.indexOf("Tunnel") == 0) PathCount = 1;
if (EntryRoom.Background.indexOf("Fork") == 0) PathCount = 1 + Math.floor(Math.random() * 3);
// Generates all paths
let Path = [];
for (let P = 0; P < PathCount; P++) {
// Generates a valid path that's not already used
let Continue = false;
let PathNum;
while (!Continue) {
if (EntryRoom.Background.indexOf("Tunnel") == 0) {
PathNum = PandoraDirectionListFrom.indexOf(DirectionFrom);
Continue = true;
}
else {
PathNum = Math.floor(Math.random() * 4);
Continue = ((PandoraDirectionList.indexOf(DirectionFrom) != PathNum) && (Path.indexOf(PathNum) < 0));
}
}
Path.push(PathNum);
// Generates a background for the room, tries not to repeat it and do not allow the same background as the previous room
let RoomBack;
Continue = false;
while (!Continue) {
RoomBack = "Cell";
let DeadEndOdds = (RoomLevel - InfiltrationDifficulty) * 0.25;
if (RoomLevel <= 2) DeadEndOdds = 0;
let TunnelOdds = 0.25 + (RoomLevel * 0.1);
if (TunnelOdds > 0.75) TunnelOdds = 0.75;
if (Math.random() >= DeadEndOdds) RoomBack = (Math.random() >= TunnelOdds) ? "Fork" : "Tunnel";
if ((RoomBack == "Cell") && (Math.random() >= 0.9)) RoomBack = "Rest0";
else RoomBack = RoomBack + Math.floor(Math.random() * 7);
Continue = (RoomBack !== EntryRoom.Background);
for (let R = 0; Continue && (R < PandoraRoom.length); R++)
if ((PandoraRoom[R].Background == RoomBack) && (Math.random() >= 0.25))
Continue = false;
}
// Creates the room
/** @type {PandoraBaseRoom} */
let Room = {};
Room.Character = [];
Room.Floor = EntryRoom.Floor;
Room.Background = RoomBack;
PandoraCreateChest(Room);
Room.Path = [];
Room.PathMap = [];
Room.PathMap.push(EntryRoom);
Room.Direction = [];
Room.DirectionMap = [];
Room.DirectionMap.push(PandoraDirectionList[PathNum]);
PandoraRoom.push(Room);
EntryRoom.PathMap.push(Room);
EntryRoom.DirectionMap.push(PandoraDirectionListFrom[PathNum]);
// Creates sub-rooms if it's not a dead end room, generate the search and rest icon if needed
if (RoomBack.indexOf("Cell") == 0) {
if ((InfiltrationMission == "Retrieve") || (InfiltrationMission == "Steal")) {
/** @type {PandoraSpecialRoom} */
let SearchRoom = { Floor: "Search" };
Room.Path.push(SearchRoom);
Room.Direction.push("Search");
}
if (InfiltrationMission == "ReverseMaid") {
/** @type {PandoraSpecialRoom} */
let PaintRoom = { Floor: "Paint" };
Room.Path.push(PaintRoom);
Room.Direction.push("Paint");
}
} else if (RoomBack.indexOf("Rest") == 0) {
/** @type {PandoraSpecialRoom} */
let RestRoom = { Floor: "Rest" };
Room.Path.push(RestRoom);
Room.Direction.push("Rest");
} else PandoraGenerateRoom(Room, PandoraDirectionListFrom[PathNum], RoomLevel + 1, MaxRoom);
}
}
/**
* Loads the Pandora's Box screen
* @param {PandoraFloors} FloorName - The name of the floor in which we must generate rooms
* @param {PandoraBaseRoom} EntryRoom - The room object that's leading to that floor
* @param {PandoraFloorDirection} DirectionFrom - The entry direction
* @param {PandoraFloorDirection} DirectionTo - The opposite direction
* @param {number} MaxRoom - The maximum limit of rooms to generate.
* @returns {void} - Nothing
*/
function PandoraGenerateFloor(FloorName, EntryRoom, DirectionFrom, DirectionTo, MaxRoom) {
// Always create the same entrance room
/** @type {PandoraBaseRoom} */
let Room = {};
Room.Character = [];
Room.Floor = FloorName;
Room.Background = "Entrance";
Room.Path = [];
Room.Path.push(EntryRoom);
Room.PathMap = [];
Room.Direction = [];
Room.Direction.push(DirectionFrom);
Room.DirectionMap = [];
PandoraRoom.push(Room);
EntryRoom.Path.push(Room);
EntryRoom.Direction.push(DirectionTo);
// Starts the room generation
PandoraGenerateRoom(Room, DirectionFrom, 1, MaxRoom);
}
/**
* Clears all the rooms and generates the main hall with an introduction maid
* @returns {void} - Nothing
*/
function PandoraBuildMainHall() {
// Creates the ground entrance room with a maid
PandoraParty = [];
/** @type {PandoraBaseRoom} */
let Room = {};
Room.Character = [];
if (InfiltrationMission != "CatBurglar") {
let Char = PandoraGenerateNPC("Entrance", "Maid", "RANDOM", false);
if (InfiltrationMission == "ReverseMaid") Char.Stage = "40";
else if (SkillGetLevel(Player, "Infiltration") >= 9) Char.Stage = "30";
else if (SkillGetLevel(Player, "Infiltration") >= 6) Char.Stage = "20";
else if (SkillGetLevel(Player, "Infiltration") >= 3) Char.Stage = "10";
Room.Character.push(Char);
}
Room.Floor = "Ground";
Room.Background = "Entrance";
Room.PathMap = [];
/** @type {PandoraSpecialRoom} */
let ExitRoom = { Floor: "Exit" };
Room.DirectionMap = [];
// Generates the floors and sets the starting room, there's a min-max number of rooms based on difficulty
let MinRoom = 25;
let MaxRoom = 39;
let MinChest = 0;
if (InfiltrationMission == "CatBurglar") MinChest = InfiltrationDifficulty + 2;
if (InfiltrationDifficulty == 1) { MinRoom = 35; MaxRoom = 59; }
if (InfiltrationDifficulty == 2) { MinRoom = 50; MaxRoom = 79; }
if (InfiltrationDifficulty == 3) { MinRoom = 70; MaxRoom = 109; }
if (InfiltrationDifficulty == 4) { MinRoom = 100; MaxRoom = 149; }
PandoraRoom = [];
while ((PandoraRoom.length < MinRoom) || (PandoraRoom.length > MaxRoom) || (PandoraChestCount < MinChest)) {
PandoraChestCount = 0;
PandoraRoom = [];
Room.Path = [];
Room.Path.push(ExitRoom);
Room.Direction = [];
Room.Direction.push("Exit");
PandoraRoom.push(Room);
PandoraGenerateFloor("Second", Room, "StairsDown", "StairsUp", MaxRoom);
PandoraGenerateFloor("Underground", Room, "StairsUp", "StairsDown", MaxRoom);
}
PandoraCurrentRoom = Room;
PandoraPreviousRoom = null;
PandoraTargetRoom = null;
// In reverse maid mission, we count the number of dead-end cell rooms that can be broken
if (InfiltrationMission == "ReverseMaid") {
PandoraReverseMaidDone = 0;
PandoraReverseMaidTotal = 0;
for (let R = 0; R < PandoraRoom.length; R++)
if (PandoraRoom[R].Background.indexOf("Cell") == 0)
PandoraReverseMaidTotal++;
}
// Pick a random cell room for the final target, generates special NPCs if needed
while (PandoraTargetRoom == null) {
Room = PandoraRoom[Math.floor(Math.random() * PandoraRoom.length)];
if (Room.Background.indexOf("Cell") == 0) {
if ((InfiltrationMission == "Retrieve") || (InfiltrationMission == "Steal")) {
Room.ItemX = 50 + Math.floor(Math.random() * 1700);
Room.ItemY = 50 + Math.floor(Math.random() * 900);
}
if (InfiltrationMission == "Rescue") {
let Victim = PandoraGenerateNPC("Rescue", "Victim", InfiltrationTarget.Name, true);
if (InfiltrationTarget.PrivateRoom) {
Victim.Appearance = PrivateRansomCharacter.Appearance.slice(0);
CharacterRefresh(Victim);
} else {
if (Math.random() >= 0.333) CharacterRandomUnderwear(Victim);
else if (Math.random() >= 0.5) CharacterNaked(Victim);
CharacterFullRandomRestrain(Victim, "LOT", true);
}
Room.Character.push(Victim);
let Guard = PandoraGenerateNPC("Rescue", "Guard", "RANDOM", false);
Room.PathMap[0].Character.push(Guard);
}
if (InfiltrationMission == "Kidnap") {
let Target = PandoraGenerateNPC("Kidnap", "Target", InfiltrationTarget.Name, true);
Room.Character.push(Target);
}
PandoraTargetRoom = Room;
}
}
}
/**
* Removes the current character from the room and deletes that NPC from the array
* @returns {void} - Nothing
*/
function PandoraRemoveCurrentCharacter() {
for (let C = 0; C < PandoraCurrentRoom.Character.length; C++)
if (PandoraCurrentRoom.Character[C].ID == CurrentCharacter.ID) {
PandoraCurrentRoom.Character.splice(C, 1);
break;
}
CharacterDelete(CurrentCharacter);
DialogLeave();
}
/**
* Flags the current character to allow moves and exits any dialog with her
* @returns {void} - Nothing
*/
function PandoraCharacterAllowMove() {
CurrentCharacter.AllowMove = true;
DialogLeave();
}
/**
* When the current character joins the player's party
* @returns {void} - Nothing
*/
function PandoraCharacterJoin() {
CurrentCharacter.AllowMove = true;
if (PandoraParty.length == 1)
PandoraCurrentRoom.Character.push(PandoraParty[0]);
PandoraParty = [];
PandoraParty.push(CurrentCharacter);
for (let C = 0; C < PandoraCurrentRoom.Character.length; C++)
if (PandoraCurrentRoom.Character[C].ID == CurrentCharacter.ID) {
PandoraCurrentRoom.Character.splice(C, 1);
break;
}
}
/**
* When the current character leaves the player's party
* @returns {void} - Nothing
*/
function PandoraCharacterLeave() {
PandoraCurrentRoom.Character.push(CurrentCharacter);
PandoraParty = [];
}
/**
* When the current character starts to fight against the player
* @returns {void} - Nothing
*/
function PandoraCharacterFight() {
PandoraFightCharacter = CurrentCharacter;
let Difficulty = (InfiltrationDifficulty * 2) + Math.floor(Math.random() * 3);
if (CurrentCharacter.AccountName.indexOf("RandomMistress") >= 0) Difficulty = InfiltrationDifficulty + 6;
KidnapStart(CurrentCharacter, PandoraBackground, Difficulty, "PandoraCharacterFightEnd()");
}
/**
* Resolves the fight between the player and the current character
* @returns {void} - Nothing
*/
function PandoraCharacterFightEnd() {
CharacterSetCurrent(PandoraFightCharacter);
SkillProgress(Player, "Willpower", KidnapSuccessWillpowerProgress(CurrentCharacter));
PandoraWillpower = Player.KidnapWillpower;
if (InfiltrationPerksActive("Recovery")) {
PandoraWillpower = PandoraWillpower + Math.round(PandoraMaxWillpower / 10);
if (PandoraWillpower > PandoraMaxWillpower) PandoraWillpower = PandoraMaxWillpower;
}
CurrentCharacter.Stage = (KidnapVictory) ? "100" : "200";
CharacterRelease(KidnapVictory ? Player : CurrentCharacter);
CurrentCharacter.AllowItem = KidnapVictory;
if (KidnapVictory) CurrentCharacter.AllowMove = true;
if (KidnapVictory && (PandoraClothes != "Random") && (PandoraClothes != "CatBurglar") && (PandoraClothes != "Cosplay")) PandoraDress(Player, PandoraClothes);
CommonSetScreen("Room", "Pandora");
CurrentCharacter.CurrentDialog = DialogFind(CurrentCharacter, (KidnapVictory) ? "FightVictory" : "FightDefeat");
}
/**
* When the mission fails, we go back to the infiltration room
* @returns {void} - Nothing
*/
function PandoraMissionFail() {
InfiltrationTarget.Fail = true;
InfiltrationTarget.Found = false;
CharacterRelease(Player);
CommonSetScreen("Room", "Infiltration");
DialogLeave();
}
/**
* When the player must walk back to the previous room he entered from
* @returns {void} - Nothing
*/
function PandoraWalkBack() {
if (PandoraPreviousRoom == null) return;
PandoraCurrentRoom = PandoraPreviousRoom;
PandoraPreviousRoom = null;
DialogLeave();
}
/**
* When the player must strips the current character
* @returns {void} - Nothing
*/
function PandoraCharacterNaked() {
CharacterNaked(CurrentCharacter);
}
/**
* When the player changes in the clothes of someone else (type)
* @param {string} Type - The type of character to dress as (ex: Guard)
* @returns {void} - Nothing
*/
function PandoraPlayerClothes(Type) {
CharacterAppearanceStripLayer(Player);
PandoraDress(Player, Type);
PandoraClothes = Type;
}
/**
* The player can only try once to recruit a random NPC, the odds are set when conversation starts
* @returns {void} - Nothing
*/
function PandoraStartRecruit() {
CurrentCharacter.Recruit = Math.random();
}
/**
* Increases the infiltration skill on some events
* @param {string|number} Progress - The progression factor
* @returns {void} - Nothing
*/
function PandoraInfiltrationChange(Progress) {
let P = parseInt(Progress);
if (InfiltrationDifficulty == 1) P = Math.round(P * 1.5);
if (InfiltrationDifficulty == 2) P = Math.round(P * 2.25);
if (InfiltrationDifficulty == 3) P = Math.round(P * 3);
if (InfiltrationDifficulty == 4) P = Math.round(P * 4);
SkillProgress(Player, "Infiltration", P);
}
/**
* Checks if the player can bring the NPC to her private room
* @returns {boolean} - Returns true if the player can
*/
function PandoraCanJoinPrivateRoom() { return PrivateHasEmptySlot(); }
/**
* When a random NPC joins the player private room, we add that character and exits the dialog
* @returns {void} - Nothing
*/
function PandoraCharacterJoinPrivateRoom() {
let C = PrivateAddCharacter(CurrentCharacter, (CurrentCharacter.Archetype === "Slave") ? "Submissive" : null, null, null, "Private");
C.FromPandora = true;
PandoraRemoveCurrentCharacter();
}
/**
* Checks if the mission is the one provided in the parameter
* @param {string} Type - The mission type
* @returns {boolean} - Returns TRUE if it's the current mission
*/
function PandoraMissionIs(Type) { return (InfiltrationMission === Type); }
/**
* Checks if the perk specified is currently selected
* @param {string} Type - The perk type
* @returns {boolean} - Returns TRUE if it's selected
*/
function PandoraHasPerk(Type) { return InfiltrationPerksActive(Type); }
/**
* Prepares an information text based on the bribe amount provided
* @param {string} Amount - The bribe amount
* @param {string} Type - The perk type
* @returns {void} - Nothing
*/
function PandoraBribeInfo(Amount, Type) {
let Money = parseInt(Amount);
CharacterChangeMoney(Player, Money * -1);
CurrentCharacter.CurrentDialog = DialogFind(CurrentCharacter, "BribeInfo" + InfiltrationMission + Type);
CurrentCharacter.CurrentDialog = CurrentCharacter.CurrentDialog.replace("LevelName", TextGet("LevelName" + PandoraTargetRoom.Floor));
let Room = PandoraTargetRoom;
while (Room.PathMap[0].Background.indexOf("Entrance") != 0)
Room = Room.PathMap[0];
let Dir = Room.DirectionMap[0];
Dir = PandoraDirectionListFrom[PandoraDirectionList.indexOf(Dir)];
CurrentCharacter.CurrentDialog = CurrentCharacter.CurrentDialog.replace("FirstDirection", TextGet("FirstDirection" + Dir));
}
/**
* Some dialog activities can boost the recruitment odds
* @returns {void} - Nothing
*/
function PandoraRecruitBoost() {
if (CurrentCharacter.RecruitOdds >= 0.75)
CurrentCharacter.RecruitOdds = CurrentCharacter.RecruitOdds - 0.05;
}
/**
* Starts the player punishment process and jumps to the punishment Dominatrix
* @param {Boolean} FromKidnapper
* @param {string} [FixIntro]
* @returns {void} - Nothing
*/
function PandoraPunishmentIntro(FromKidnapper, FixIntro) {
let IntroText = FixIntro;
if (FixIntro == null) {
if (SkillGetLevel(Player, "Infiltration") >= 8) IntroText = DialogFind(CurrentCharacter, "Punishment8");
else if (SkillGetLevel(Player, "Infiltration") >= 5) IntroText = DialogFind(CurrentCharacter, "Punishment5");
else if (SkillGetLevel(Player, "Infiltration") >= 2) IntroText = DialogFind(CurrentCharacter, "Punishment2");
else IntroText = DialogFind(CurrentCharacter, "Punishment0");
}
PandoraBackground = "Pandora/Underground/Cell" + Math.floor(Math.random() * 7).toString();
let Dominatrix = PandoraGenerateNPC("Punishment", "Mistress", "RANDOM", false);
if (SkillGetLevel(Player, "Infiltration") >= 2) Dominatrix.Stage = "20";
if (SkillGetLevel(Player, "Infiltration") >= 5) Dominatrix.Stage = "50";
if (SkillGetLevel(Player, "Infiltration") >= 8) Dominatrix.Stage = "80";
if ((FromKidnapper != null) && FromKidnapper) Dominatrix.Stage = "200";
CharacterSetCurrent(Dominatrix);
CurrentCharacter.CurrentDialog = IntroText;
if (InfiltrationTarget) {
InfiltrationTarget.Fail = true;
InfiltrationTarget.Found = false;
}
}
/**
* Puts the player in lots of random restraints
* @returns {void} - Nothing
*/
function PandoraRestrainPlayer() {
CharacterFullRandomRestrain(Player, "LOT", true);
}
/**
* When the player purchases a drink from the maid, she can heal by the money value
* @param {number} Money
* @returns {void} - Nothing
*/
function PandoraBuyMaidDrink(Money) {
Money = parseInt(Money);
CharacterChangeMoney(Player, Money * -1);
if (CurrentCharacter.DrinkValue == null) CurrentCharacter.DrinkValue = 0;
if (Money > CurrentCharacter.DrinkValue) {
PandoraWillpower = PandoraWillpower + Money - CurrentCharacter.DrinkValue;
if (PandoraWillpower > PandoraMaxWillpower) PandoraWillpower = PandoraMaxWillpower;
CurrentCharacter.DrinkValue = Money;
}
}
/**
* Generates new random odds for a character, based on Pandora's difficulty
* @returns {void} - Nothing
*/
function PandoraCharacterGenerateRandomOdds() {
CurrentCharacter.RandomOdds = Math.random() + 0.2 - (InfiltrationDifficulty * 0.1);
}
/**
* Starts the guard quiz, the player needs 5 good answers to be let go
* @returns {void} - Nothing
*/
function PandoraQuizStart() {
CurrentCharacter.QuizLog = [];
CurrentCharacter.QuizFail = 0;
PandoraQuizNext();
}
/**
* Generates questions that guards will challenge the player with
* @returns {void} - Nothing
*/
function PandoraQuizNext() {
let Question = Math.floor(Math.random() * 11);
while (CurrentCharacter.QuizLog.indexOf(Question) >= 0)
Question = Math.floor(Math.random() * 11);
CurrentCharacter.QuizLog.push(Question);
CurrentCharacter.CurrentDialog = DialogFind(CurrentCharacter, "QuizQuestion" + Question.toString());
}
/**
* When the player gives an answer to the guard quiz, the guard will give a visual hint if the answer is incorrect
* @param {string} Answer
* @returns {void} - Nothing
*/
function PandoraQuizAnswer(Answer) {
if (Answer != "1") {
CurrentCharacter.QuizFail++;
CharacterSetFacialExpression(CurrentCharacter, "Blush", "Low", 3);
CharacterSetFacialExpression(CurrentCharacter, "Eyes", "Angry", 3);
CharacterSetFacialExpression(CurrentCharacter, "Eyes2", "Angry", 3);
}
if (CurrentCharacter.QuizLog.length >= 5) {
CurrentCharacter.Stage = (CurrentCharacter.QuizFail >= 2) ? "Arrest" : "30";
CurrentCharacter.CurrentDialog = DialogFind(CurrentCharacter, (CurrentCharacter.QuizFail >= 2) ? "QuizFail" : "QuizSuccess");
} else PandoraQuizNext();
}
/**
* When the player gets ungagged by an NPC, we remove everything on the head
* @returns {void} - Nothing
*/
function PandoraPlayerUngag() {
InventoryRemove(Player, "ItemHead");
InventoryRemove(Player, "ItemHood");
InventoryRemove(Player, "ItemNose");
InventoryRemove(Player, "ItemMouth");
InventoryRemove(Player, "ItemMouth2");
InventoryRemove(Player, "ItemMouth3");
}
/**
* Sets the punishment sentence in minutes
* @param {string|number} Minutes
* @returns {void} - Nothing
*/
function PandoraPunishmentSentence(Minutes) {
Player.Infiltration.Punishment = {
Minutes: parseInt(Minutes),
Background: PandoraBackground,
Difficulty: InfiltrationDifficulty,
};
}
/**
* Starts the single-player punishment
* @returns {void} - Nothing
*/
function PandoraPunishmentStart() {
PandoraWillpower = 0;
Player.Infiltration.Punishment.Timer = CurrentTime + (Player.Infiltration.Punishment.Minutes * 60000);
ServerSend("AccountUpdate", { Infiltration: Player.Infiltration });
DialogLeave();
CommonSetScreen("Room", "PandoraPrison");
}
/**
* Prepares the multi-player punishment
* @returns {void} - Nothing
*/
function PandoraPenitentiaryPrepare() {
PandoraPenitentiaryStart(Player.Infiltration.Punishment.Minutes, "I");
}
/**
* Starts the multi-player punishment
* @param {number} Minutes - The number of minutes for the sentence
* @param {string} Role - The role to take (I for Inmate, G for guard)
* @returns {void} - Nothing
*/
function PandoraPenitentiaryStart(Minutes, Role) {
if (!PandoraPenitentiaryIsGuard(Player) && !PandoraPenitentiaryIsInmate(Player)) {
if (Player.Game.Prison == null) Player.Game.Prison = {};
Player.Game.Prison.Timer = Math.round(CurrentTime + (Minutes * 60 * 1000));
Player.Game.Prison.Role = Role;
ServerAccountUpdate.QueueData({ Game: Player.Game }, true);
}
DialogLeave();
CommonSetScreen("Room", "MainHall");
}
/**
* Checks if the current character is currently a Pandora Penitentiary inmate (I)
* @param {Character} C - The character to evaluate
* @returns {boolean} - TRUE if sentence is active and role is inmate
*/
function PandoraPenitentiaryIsInmate(C) {
return (C.Game != null) && (C.Game.Prison != null) && (C.Game.Prison.Timer != null) && (C.Game.Prison.Timer > CurrentTime) && ((C.Game.Prison.Role == null) || (C.Game.Prison.Role === "I"));
}
/**
* Checks if the current character is currently a Pandora Penitentiary guard (G)
* @param {Character} C - The character to evaluate
* @returns {boolean} - TRUE if shift is active and role is guard
*/
function PandoraPenitentiaryIsGuard(C) {
return (C.Game != null) && (C.Game.Prison != null) && (C.Game.Prison.Timer != null) && (C.Game.Prison.Timer > CurrentTime) && (C.Game.Prison.Role === "G");
}
/**
* Called from MainHall and ChatSearch - Checks if we must create the Pandora prison and creates it
* @returns {void} - Nothing
*/
function PandoraPenitentiaryCreate() {
// Exits right away if the prison should not be loaded, there's a 10 seconds delay between each retry
if ((PandoraPenitentiaryCreateTimer > CommonTime()) || (CurrentCharacter != null) || (CurrentScreen == "ChatRoom")) return;
if (!PandoraPenitentiaryIsInmate(Player) && !PandoraPenitentiaryIsGuard(Player)) return;
// The guard can be assigned to create a new prison cell
if (PandoraPenitentiaryIsGuard(Player)) {
if (PandoraPenitentiaryStartNewRoom) {
PandoraPenitentiaryResult([]);
PandoraPenitentiaryStartNewRoom = false;
}
} else {
// The inmate first search for a prison that already exists
/** @type {ServerChatRoomGame} */
let Game = "Prison";
/** @type {ServerChatRoomSearchRequest} */
const SearchData = { Query: "", Language: "", Space: ChatRoomSpaceType.MIXED, Game: Game, FullRooms: false };
ChatAdminBackgroundList = BackgroundsGenerateList([BackgroundsTagPandora]);
ChatSearchLastQuerySearchTime = CommonTime();
ChatSearchResult = [];
PandoraPenitentiaryCreateTimer = CommonTime() + 10000;
PandoraPenitentiaryActivityTimer = CommonTime() + 60000 + Math.random() * 240000;
ServerSend("ChatRoomSearch", SearchData);
}
}
/**
* Called from MainHall and ChatSearch - Checks if we must create the Pandora prison and creates it
* @param {object} Result - The result of the search
* @returns {void} - Nothing
*/
function PandoraPenitentiaryResult(Result) {
// Exits right away if already in a chat room
if (CurrentScreen == "ChatRoom") return;
// Prepares a list of all available prisons, do not return in a room where we used a safeword
let List = [];
for (let R of Result)
if (R.Name.startsWith("Pandora") && !PandoraPenitentiarySafewordRooms.includes(R.Name) && (R.Game == "Prison") && (R.MemberCount < R.MemberLimit))
List.push(R.Name);
// If we find some results
if (List.length > 0) {
// Try to join a random prison
ServerSend("ChatRoomJoin", { Name: CommonRandomItemFromList(null, List) });
} else {
// Since we did not found any prison, we Setup the parameters for a new one
ChatRoomSpace = ChatRoomSpaceType.MIXED;
ChatSearchReturnToScreen = null;
PandoraPenitentiaryCreateTimer = CommonTime() + 10000;
// Creates the prison chat room
/** @type {ChatRoomSettings} */
const NewRoom = {
Name: "Pandora " + Math.round(Math.random() * 1000000000).toString(),
Description: "Pandora Penitentiary Cell",
Background: CommonRandomItemFromList(null, ["PrisonHall", "PandoraCell0", "PandoraCell1", "PandoraCell2", "PandoraCell3", "PandoraCell4", "PandoraCell5", "PandoraCell6"]),
Visibility: ChatRoomVisibilityMode.PUBLIC,
Access: ChatRoomAccessMode.PUBLIC,
Limit: 10,
Space: ChatRoomSpaceType.MIXED,
Game: "Prison",
Admin: [],
Whitelist: [],
Ban: [],
BlockCategory: ["Leashing"],
Language: ChatAdminDefaultLanguage,
};
ServerSend("ChatRoomCreate", NewRoom);
}
}
/**
* Returns TRUE if the group is available for an Onlin Pandora Prison activity
* @param {Character} C - The character to evaluate
* @param {AssetGroupItemName} Group - The item group name
* @param {boolean} SkipBlock - If we should skip the GroupIsBlocked validation
* @returns {boolean} - TRUE if the group is available
*/
function PandoraGroupAvailable(C, Group, SkipBlock = false) {
let Item = InventoryGet(C, Group);
if (Item == null) return true;
if (!SkipBlock && InventoryGroupIsBlocked(C, Group)) return false;
return !InventoryOwnerOnlyItem(Item) && !InventoryLoverOnlyItem(Item) && !InventoryFamilyOnlyItem(Item);
}
/**
* Sets a proper minimal difficulty for the new item set in Online Pandora Prison
* @param {Character} C - The character to evaluate
* @param {AssetGroupItemName} Group - The item group name
* @param {number} Level - The infiltration level, the higher it gets the harder the difficulty will be
* @returns {void} - Nothing
*/
function PandoraPenitentiaryItemDifficulty(C, Group, Level) {
let Item = InventoryGet(C, Group);
if (Item == null) return;
Level = Level + (((Group == "ItemArms") || (Group == "ItemDevices")) ? 6 : 3);
let Difficulty = Math.floor(Level + Math.random() * Level * 0.5);
if ((Item.Difficulty == null) || (Item.Difficulty < Difficulty)) Item.Difficulty = Difficulty;
}
/**
* Builds a list of all valid Pandora activities for the player
* @param {Character} C - The character to build a list for
* @returns {readonly string[]} - An array of all valid activities for the player
*/
function PandoraPenitentiaryBuildActivityList(C) {
// Picks a random activity to do on the player
let Level = SkillGetLevel(Player, "Infiltration");
let ActivityList = ["Bondage", "Spank", "Hose"];
let Device = InventoryGet(C, "ItemDevices");
let InPandoraCase = ((Device != null) && (Device.Asset != null) && (Device.Asset.Name != null) && (PandoraPenitentiaryCageList.includes(Device.Asset.Name)));
// Adds more possible activities to the random pool
if (!C.IsGagged()) ActivityList.push("Gag");
if (C.IsGagged() && PandoraGroupAvailable(C, "ItemMouth", true) && PandoraGroupAvailable(C, "ItemMouth2", true) && PandoraGroupAvailable(C, "ItemMouth3", true) && PandoraGroupAvailable(C, "ItemHead", true) && PandoraGroupAvailable(C, "ItemHood", true)) ActivityList.push("Ungag");
if (!C.IsEnclose() && !InPandoraCase && (C.PoseMapping.BodyFull !== "Hogtied") && (Level >= 3) && PandoraGroupAvailable(C, "ItemArms")) ActivityList.push("Hogtie");
if (!C.IsEnclose() && !InPandoraCase && !InventoryIsWorn(C, "HempRope", "ItemArms") && PandoraGroupAvailable(C, "ItemArms")) ActivityList.push("Shibari");
if (!InventoryIsWorn(C, "PrisonLockdownSuit", "ItemArms") && !InventoryIsWorn(C, "TransportJacket", "ItemArms") && !InventoryIsWorn(C, "FullLatexSuit", "ItemArms") && !InventoryIsWorn(C, "PrisonSJ", "ItemArms") && !C.HasPenis() && PandoraGroupAvailable(C, "ItemArms")) ActivityList.push("Detain");
if (!C.IsNaked()) ActivityList.push("Naked");
if (!C.IsVulvaChaste() && !C.HasPenis() && (InventoryGet(C, "ItemPelvis") == null) && !InventoryGroupIsBlocked(C, "ItemPelvis")) ActivityList.push("Chastity");
if (C.IsVulvaChaste() && !C.HasPenis() && PandoraGroupAvailable(C, "ItemPelvis")) ActivityList.push("Unchaste");
if (InventoryHasLockableItems(C)) ActivityList.push("Lock");
if (InventoryCharacterIsWearingLock(C, "MetalPadlock") || InventoryCharacterIsWearingLock(C, "IntricatePadlock") || InventoryCharacterIsWearingLock(C, "ExclusivePadlock") || InventoryCharacterIsWearingLock(C, "HighSecurityPadlock") || InventoryCharacterIsWearingLock(C, "PandoraPadlock")) ActivityList.push("Unlock");
if (C.IsRestrained()) ActivityList.push("Release");
if (Device == null) ActivityList.push("Cage");
if (InPandoraCase && PandoraGroupAvailable(C, "ItemDevices")) ActivityList.push("Uncage");
// Returns the full list of all valid activities
return ActivityList;
}
/**
* Runs the selected activity inmate activity for the player
* @param {string} Activity - The activity to run
* @returns {void} - Nothing
*/
function PandoraPenitentiaryActivityRun(Activity) {
// Sets the next random guard activity time
PandoraPenitentiaryActivityTimer = CommonTime() + 60000 + Math.random() * 240000;
// The higher the level, the stronger the lock type to use
let Level = SkillGetLevel(Player, "Infiltration");
/** @type {AssetLockType} */
let LockType = "MetalPadlock";
if (Level >= 8) LockType = "PandoraPadlock";
else if (Level >= 6) LockType = "HighSecurityPadlock";
else if (Level >= 4) LockType = "ExclusivePadlock";
else if (Level >= 2) LockType = "IntricatePadlock";
// A few activities releases some locks first
if ((Activity == "Unlock") || (Activity == "Release") || (Activity == "Bondage") || (Activity == "Shibari") || (Activity == "Detain") || (Activity == "Hogtie") || (Activity == "Naked")) {
CharacterReleaseFromLock(Player, "MetalPadlock");
CharacterReleaseFromLock(Player, "IntricatePadlock");
CharacterReleaseFromLock(Player, "ExclusivePadlock");
CharacterReleaseFromLock(Player, "HighSecurityPadlock");
CharacterReleaseFromLock(Player, "PandoraPadlock");
}
// Bondage - Puts the player in restraints, heavier restraints based on inflitration level
if (Activity == "Bondage") {
CharacterReleaseNoLock(Player);
if (Level <= 1) CharacterFullRandomRestrain(Player, "FEW");
else if (Level <= 4) CharacterFullRandomRestrain(Player);
else if (Level <= 7) CharacterFullRandomRestrain(Player, "LOT");
else CharacterFullRandomRestrain(Player, "ALL");
PandoraPenitentiaryItemDifficulty(Player, "ItemArms", Level);
PandoraPenitentiaryItemDifficulty(Player, "ItemHead", Level);
PandoraPenitentiaryItemDifficulty(Player, "ItemMouth", Level);
PandoraPenitentiaryItemDifficulty(Player, "ItemNeck", Level);
PandoraPenitentiaryItemDifficulty(Player, "ItemLegs", Level);
PandoraPenitentiaryItemDifficulty(Player, "ItemFeet", Level);
}
// Spank - Spanks the player - Only changes the facial expression
if (Activity == "Spank") {
CharacterSetFacialExpression(Player, "Blush", "High", 15);
CharacterSetFacialExpression(Player, "Eyes", "Surprised", 15);
}
// Hose - Hoses the player with cold water - Only changes the facial expression
if (Activity == "Hose") {
CharacterSetFacialExpression(Player, "Blush", "ShortBreath", 15);
CharacterSetFacialExpression(Player, "Eyes", "Angry", 15);
}
// Shibari - Strips the player and rope her up
if (Activity == "Shibari") {
CharacterReleaseNoLock(Player);
CharacterNaked(Player);
ShibariRandomBondage(Player, Math.floor((Level / 3) + 1));
PandoraPenitentiaryItemDifficulty(Player, "ItemArms", Level);
}
// Detain - Dress the player in prison clothes / restraints
if (Activity == "Detain") {
CharacterReleaseNoLock(Player);
CharacterNaked(Player);
InventoryWear(Player, CommonRandomItemFromList(null, ["PrisonLockdownSuit", "TransportJacket", "FullLatexSuit", "PrisonSJ"]), "ItemArms");
InventoryLock(Player, "ItemArms", LockType);
PandoraPenitentiaryItemDifficulty(Player, "ItemArms", Level);
if ((Level >= 2) && (InventoryGet(Player, "ItemFeet") == null) && !InventoryGroupIsBlocked(Player, "ItemFeet")) {
InventoryWear(Player, CommonRandomItemFromList(null, ["BallChain", "AnkleShackles"]), "ItemFeet");
InventoryLock(Player, "ItemFeet", LockType);
PandoraPenitentiaryItemDifficulty(Player, "ItemFeet", Level);
}
if ((Level >= 5) && (InventoryGet(Player, "ItemMouth") == null) && !InventoryGroupIsBlocked(Player, "ItemMouth")) {
InventoryWear(Player, "PrisonLockdownGag", "ItemMouth");
InventoryLock(Player, "ItemMouth", LockType);
PandoraPenitentiaryItemDifficulty(Player, "ItemMouth", Level);
}
if ((Level >= 8) && (InventoryGet(Player, "ItemHead") == null) && !InventoryGroupIsBlocked(Player, "ItemHead")) {
InventoryWear(Player, "PrisonLockdownBlindfold", "ItemHead");
InventoryLock(Player, "ItemHead", LockType);
PandoraPenitentiaryItemDifficulty(Player, "ItemHead", Level);
}
}
// Gag - Gags the player with a random mouth item
if (Activity == "Gag") {
InventoryWearRandom(Player, "ItemMouth");
InventoryLock(Player, "ItemMouth", LockType);
PandoraPenitentiaryItemDifficulty(Player, "ItemMouth", Level);
}
// Ungag - Remove hoods and mouth items from the player
if (Activity == "Ungag") {
InventoryRemove(Player, "ItemMouth");
InventoryRemove(Player, "ItemMouth2");
InventoryRemove(Player, "ItemMouth3");
InventoryRemove(Player, "ItemHead");
InventoryRemove(Player, "ItemHood");
}
// Hogtie - Puts the player in chains and hogtie her, at higher levels she gets suspended
if (Activity == "Hogtie") {
// Adds chains to the player and locks them
CharacterReleaseNoLock(Player);
if (InventoryGet(Player, "ItemArms") == null) {
InventoryWear(Player, "Chains", "ItemArms");
InventoryLock(Player, "ItemArms", LockType);
PandoraPenitentiaryItemDifficulty(Player, "ItemArms", Level);
}
if (InventoryGet(Player, "ItemArms") == null) {
InventoryWear(Player, "Chains", "ItemLegs");
InventoryLock(Player, "ItemLegs", LockType);
PandoraPenitentiaryItemDifficulty(Player, "ItemLegs", Level);
}
if (InventoryGet(Player, "ItemFeet") == null) {
InventoryWear(Player, "Chains", "ItemFeet");
InventoryLock(Player, "ItemFeet", LockType);
PandoraPenitentiaryItemDifficulty(Player, "ItemFeet", Level);
}
// At level 7 or more the player gets suspended
if (Level < 7) {
TypedItemSetOptionByName(Player, "ItemArms", "Hogtied");
} else {
const item = InventoryGet(Player, "ItemArms");
TypedItemSetOptionByName(Player, item, "SuspensionHogtied");
const Height = 0.67 * Math.random();
item.Property.OverrideHeight = {
Height: Height * Pose.find(p => p.Name == "Hogtied").OverrideHeight.Height,
HeightRatioProportion: Height,
Priority: 0,
};
}
}
// Naked - Strips the player and prevents changing for (Level + 2) minutes
if (Activity == "Naked") {
CharacterReleaseNoLock(Player);
CharacterNaked(Player);
let Time = CurrentTime + 60000 * (Level + 2);
if (Time > Player.Game.Prison.Timer) Time = Player.Game.Prison.Timer;
LogAdd("BlockChange", "Rule", Time);
}
// Lock - Locks all lockable items of the player in a specific lock
if (Activity == "Lock") {
InventoryFullLock(Player, LockType);
}
// Chastity - Puts the player in a chastity belt and locks it
if (Activity == "Chastity") {
InventoryWearRandom(Player, "ItemPelvis", null, null, false, true, PrivateBeltList, true);
InventoryLock(Player, "ItemPelvis", LockType, null);
PandoraPenitentiaryItemDifficulty(Player, "ItemPelvis", Level);
}
// Unchaste - Removes the chastity belt from the player
if (Activity == "Unchaste") {
InventoryRemove(Player, "ItemPelvis");
}
// Release - Releases the player from all non-locked restraints
if (Activity == "Release") {
CharacterReleaseNoLock(Player);
}
// Cage - Puts the player in a random cage from ItemDevices group
if (Activity == "Cage") {
InventoryWear(Player, CommonRandomItemFromList(null, PandoraPenitentiaryCageList), "ItemDevices");
PandoraPenitentiaryItemDifficulty(Player, "ItemDevices", Level);
}
// Uncage - Releases the player from the ItemDevices group
if (Activity == "Uncage") {
InventoryRemove(Player, "ItemDevices");
}
// Publishes the activity to everyone in chat
ChatRoomCharacterUpdate(Player);
}
/**
* Runs the pandora prison events
* @returns {void} - Nothing
*/
function PandoraPenitentiaryRun() {
// Exits right away if Pandora Penitentiary isn't active or not ready to run
if (!PandoraPenitentiaryIsInmate(Player) || (CurrentScreen != "ChatRoom") || (PandoraPenitentiaryActivityTimer > CommonTime())) return;
// Finds a proper activity based on the player current predicament and infiltration level
let ActivityList = PandoraPenitentiaryBuildActivityList(Player);
let Activity = CommonRandomItemFromList(null, ActivityList);
// Runs and publishes the activity to everyone in chat
PandoraPenitentiaryActivityRun(Activity);
const Dict = new DictionaryBuilder().targetCharacter(Player).build();
ServerSend("ChatRoomChat", { Content: "PandoraActivity" + Activity, Type: "Action", Dictionary: Dict });
}
/**
* Runs the pandora command from the online chat box
* @param {string} Activity - The activity to validate
* @returns {void} - Nothing
*/
function PandoraPenitentiaryDoActivity(Activity) {
// Make sur the source/player is a guard first
if (!PandoraPenitentiaryIsGuard(Player))
return ChatRoomSendLocal(TextGet("PandoraInvalidSource"), 10_000);
// Make sure the target is an inmate
let Target = ChatRoomCharacter.find(c => c.MemberNumber === ChatRoomTargetMemberNumber);
if ((Target != null) && !PandoraPenitentiaryIsInmate(Target))
return ChatRoomSendLocal(TextGet("PandoraInvalidTarget"), 10_000);
// Make sure the activity is valid
if (Activity != null) {
Activity = Activity.trim();
Activity = Activity.charAt(0).toUpperCase() + Activity.substring(1).toLowerCase();
}
if ((Activity == null) || !PandoraPenitentiaryActivityList.includes(Activity))
return ChatRoomSendLocal(TextGet("PandoraInvalidActivity"), 10_000);
// Make sure the activity can be run on that target
if (Target != null) {
let ActivityList = PandoraPenitentiaryBuildActivityList(Target);
if (!ActivityList.includes(Activity))
return ChatRoomSendLocal(TextGet("PandoraInvalidActivityForTarget"), 10_000);
}
// Tries to find a target and publishes the activity in the local chat
if (Target == null) {
let Dict = new DictionaryBuilder().sourceCharacter(Player).build();
ServerSend("ChatRoomChat", { Content: "PandoraGuardActivity" + Activity + "All", Type: "Action", Dictionary: Dict });
ServerSend("ChatRoomChat", { Content: "Pandora" + Activity + "|ALL", Type: "Hidden"});
} else {
let Dict = new DictionaryBuilder().sourceCharacter(Player).targetCharacter(Target).build();
ServerSend("ChatRoomChat", { Content: "PandoraGuardActivity" + Activity, Type: "Action", Dictionary: Dict });
ServerSend("ChatRoomChat", { Content: "Pandora" + Activity + "|" + Target.MemberNumber, Type: "Hidden", Target: Target.MemberNumber});
}
}
/**
* Called from chat room, processes hidden Pandora Prison messages
* @param {Character} SenderCharacter - The character sending the message
* @param {String} Interaction - The message sent
* @returns {void} - Nothing to be used
*/
function PandoraPenitentiaryHiddenMessage(SenderCharacter, Interaction) {
// Make sure we can process the Pandora packet
if (!Interaction.startsWith("Pandora") || (Interaction.indexOf("|") <= 0) || !PandoraPenitentiaryIsInmate(Player) || !PandoraPenitentiaryIsGuard(SenderCharacter) || SenderCharacter.IsBlacklisted()) return;
// Make sure we target the player (ALL targets everyone)
let Target = Interaction.split("|")[1];
if ((Target != "ALL") && (Target != Player.MemberNumber.toString())) return;
// Validates the activity before running it
let Activity = Interaction.split("|")[0].substring(7);
if (!PandoraPenitentiaryActivityList.includes(Activity)) return;
let ActivityList = PandoraPenitentiaryBuildActivityList(Player);
if (!ActivityList.includes(Activity)) return;
// Runs the activity on the player, without publishing it
PandoraPenitentiaryActivityRun(Activity);
}
/**
* When an NPC pays the player for a service
* @param {string} Amount - The paid amount
* @returns {void} - Nothing
*/
function PandoraPlayerPay(Amount) {
CharacterChangeMoney(Player, parseInt(Amount));
}
/**
* When the player pays an NPC to wear her clothes
* @returns {void} - Nothing
*/
function PandoraBuyRandomClothes() {
CharacterChangeMoney(Player, -10);
CharacterNaked(Player);
CharacterTransferItem(CurrentCharacter, Player, "Cloth");
CharacterTransferItem(CurrentCharacter, Player, "ClothLower");
CharacterTransferItem(CurrentCharacter, Player, "Bra");
CharacterTransferItem(CurrentCharacter, Player, "Panties");
CharacterTransferItem(CurrentCharacter, Player, "Socks");
CharacterTransferItem(CurrentCharacter, Player, "Shoes");
CharacterNaked(CurrentCharacter);
PandoraClothes = "Random";
}
/**
* Starts the chest lockpicking mini-game
* @returns {void} - Nothing
*/
function PandoraChestLockpickStart() {
ChestLockpickChestImage = ((CurrentCharacter == null) || (CurrentCharacter.FixedImage == null)) ? "Screens/Room/Pandora/Chest" + Math.floor(Math.random() * 3).toString() + ".png" : CurrentCharacter.FixedImage;
ChestLockpickBackground = PandoraBackground;
DialogLeave();
MiniGameStart("ChestLockpick", InfiltrationDifficulty, "PandoraChestLockEnd");
}
/**
* When the picklock mini-game ends, adds 30 seconds to the timer
* @returns {void} - Nothing
*/
function PandoraChestLockEnd() {
PandoraTimer = PandoraTimer - 30000;
PandoraCurrentRoom.Character[0].Stage = (MiniGameVictory) ? "100" : "0";
CharacterSetCurrent(PandoraCurrentRoom.Character[0]);
CommonSetScreen("Room", "Pandora");
}
/**
* When the player loots the chest, we add some money and the chest disappear
* @returns {void} - Nothing
*/
function PandoraChestLoot() {
// Gets the regular loot
DialogLeave();
PandoraMoney = PandoraMoney + Math.floor((5 + Math.random() * 5) * (InfiltrationDifficulty + 2) * 0.5);
PandoraChestCount--;
PandoraCurrentRoom.Character.splice(0, 1);
// Can also get a special card from the chest (higher odds with higher difficulty)
if ((Math.random() < InfiltrationDifficulty / 25) && (Player.Game != null) && (Player.Game.ClubCard != null) && (Player.Game.ClubCard.Reward != null)) {
let Char = String.fromCharCode(30022);
if (Player.Game.ClubCard.Reward.indexOf(Char) < 0) {
Player.Game.ClubCard.Reward = Player.Game.ClubCard.Reward + Char;
ServerAccountUpdate.QueueData({ Game: Player.Game }, true);
PandoraMsgBox(TextGet("FoundPandoraBoxClubCard"));
}
}
}
/**
* When the player gets the paint cans from a cosplay girl
* @returns {void} - Nothing
*/
function PandoraGetPaint() {
PandoraPaint = true;
}
/**
* When the player starts a club card game
* @returns {void} - Nothing
*/
function PandoraClubCardStart() {
PandoraFightCharacter = CurrentCharacter;
ClubCardOpponent = CurrentCharacter;
ClubCardOpponentDeck = ClubCardBuilderDominantDeck;
MiniGameStart("ClubCard", 0, "PandoraClubCardEnd");
}
/**
* When the player ends a club card game
* @returns {void} - Nothing
*/
function PandoraClubCardEnd() {
CommonSetScreen("Room", "Pandora");
CharacterSetCurrent(PandoraFightCharacter);
CurrentCharacter.CurrentDialog = DialogFind(CurrentCharacter, MiniGameVictory ? "ClubCardVictory" : "ClubCardDefeat");
}