mirror of
https://gitgud.io/BondageProjects/Bondage-College.git
synced 2025-04-25 17:59:34 +00:00
3729 lines
160 KiB
JavaScript
3729 lines
160 KiB
JavaScript
"use strict";
|
|
var ChatRoomBackground = "";
|
|
var ChatRoomData = {};
|
|
/** @type {Character[]} */
|
|
var ChatRoomCharacter = [];
|
|
var ChatRoomChatLog = [];
|
|
var ChatRoomLastMessage = [""];
|
|
var ChatRoomLastMessageIndex = 0;
|
|
var ChatRoomTargetMemberNumber = null;
|
|
var ChatRoomOwnershipOption = "";
|
|
var ChatRoomLovershipOption = "";
|
|
var ChatRoomPlayerCanJoin = false;
|
|
var ChatRoomMoneyForOwner = 0;
|
|
var ChatRoomQuestGiven = [];
|
|
var ChatRoomSpace = "";
|
|
var ChatRoomGame = "";
|
|
var ChatRoomMoveTarget = null;
|
|
var ChatRoomHelpSeen = false;
|
|
var ChatRoomAllowCharacterUpdate = true;
|
|
var ChatRoomStruggleAssistBonus = 0;
|
|
var ChatRoomStruggleAssistTimer = 0;
|
|
var ChatRoomSlowtimer = 0;
|
|
var ChatRoomSlowStop = false;
|
|
var ChatRoomChatHidden = false;
|
|
|
|
var ChatRoomCharacterCount = 0;
|
|
var ChatRoomCharacterDrawlist = [];
|
|
var ChatRoomSenseDepBypass = false;
|
|
|
|
var ChatRoomGetUpTimer = 0;
|
|
var ChatRoomLastName = "";
|
|
var ChatRoomLastBG = "";
|
|
var ChatRoomLastPrivate = false;
|
|
var ChatRoomLastSize = 0;
|
|
var ChatRoomLastDesc = "";
|
|
var ChatRoomLastAdmin = [];
|
|
var ChatRoomLastBan = [];
|
|
var ChatRoomNewRoomToUpdate = null;
|
|
var ChatRoomNewRoomToUpdateTimer = 0;
|
|
var ChatRoomLeashList = [];
|
|
var ChatRoomLeashPlayer = null;
|
|
var ChatRoomTargetDirty = false;
|
|
// Chance of a chat message popping up reminding you of your plugs/crotch rope at 0 arousal. Chance is for each item, but only one message appears, with priority to ones with higher chance
|
|
const ChatRoomArousalMsg_Chance = {
|
|
"Kneel" : 0.1,
|
|
"Walk" : 0.33,
|
|
"StruggleFail" : 0.4,
|
|
"StruggleAction" : 0.05,
|
|
"Gag" : 0,
|
|
};
|
|
const ChatRoomArousalMsg_ChanceScaling = {
|
|
"Kneel" : 0.8,
|
|
"Walk" : 0.67,
|
|
"StruggleFail" : 0.4,
|
|
"StruggleAction" : 0.2,
|
|
"Gag" : 0,
|
|
};
|
|
const ChatRoomArousalMsg_ChanceVibeMod = {
|
|
"Kneel" : 0.0,
|
|
"Walk" : 0.8,
|
|
"StruggleFail" : 0.6,
|
|
"StruggleAction" : 0.3,
|
|
"Gag" : 0,
|
|
};
|
|
const ChatRoomArousalMsg_ChanceInflationMod = {
|
|
"Kneel" : 0.1,
|
|
"Walk" : 0.5,
|
|
"StruggleFail" : 0.4,
|
|
"StruggleAction" : 0.2,
|
|
"Gag" : 0,
|
|
};
|
|
const ChatRoomArousalMsg_ChanceGagMod = {
|
|
"Kneel" : 0,
|
|
"Walk" : 0,
|
|
"StruggleFail" : 0,
|
|
"StruggleAction" : 0,
|
|
"Gag" : 0.3,
|
|
};
|
|
var ChatRoomPinkFlashTime = 0;
|
|
var ChatRoomPinkFlashColor = "#FFB0B0";
|
|
var ChatRoomPinkFlashAlphaStrength = 140;
|
|
var ChatRoomHideIconState = 0;
|
|
var ChatRoomMenuButtons = [];
|
|
let ChatRoomFontSize = 30;
|
|
const ChatRoomFontSizes = {
|
|
Small: 28,
|
|
Medium: 36,
|
|
Large: 44,
|
|
};
|
|
|
|
var ChatRoomCharacterX_Upper = 0;
|
|
var ChatRoomCharacterX_Lower = 0;
|
|
var ChatRoomCharacterZoom = 1;
|
|
var ChatRoomSlideWeight = 9;
|
|
var ChatRoomCharacterInitialize = true;
|
|
|
|
/**
|
|
* Chat room resize manager object: Handles resize events for the chat log.
|
|
* @constant
|
|
* @type {object} - The chat room resize manager object. Contains the functions and properties required to handle
|
|
* resize events.
|
|
*/
|
|
let ChatRoomResizeManager = {
|
|
atStart : true, // Is this the first event in a chain of resize events?
|
|
timer : null, // Timer that triggers the end function after no resize events have been received recently.
|
|
timeOut : 200, // The amount of milliseconds that has to pass before the chain of resize events is considered over and the timer is called.
|
|
ChatRoomScrollPercentage : 0, // Height of the chat log scroll bar before the first resize event occurs, as a percentage.
|
|
ChatLogScrolledToEnd : false, // Is the chat log scrolled all the way to the end before the first resize event occurs?
|
|
|
|
// Triggered by resize event
|
|
ChatRoomResizeEvent : function() {
|
|
if(ChatRoomResizeManager.atStart) { // Run code for the first resize event in a chain of resize events.
|
|
ChatRoomResizeManager.ChatRoomScrollPercentage = ElementGetScrollPercentage("TextAreaChatLog");
|
|
ChatRoomResizeManager.ChatLogScrolledToEnd = ElementIsScrolledToEnd("TextAreaChatLog");
|
|
ChatRoomResizeManager.atStart = false;
|
|
}
|
|
|
|
// Reset timer if an event was received recently.
|
|
if (ChatRoomResizeManager.timer) clearTimeout(ChatRoomResizeManager.timer);
|
|
ChatRoomResizeManager.timer = setTimeout(ChatRoomResizeManager.ChatRoomResizeEventsEnd, ChatRoomResizeManager.timeOut);
|
|
},
|
|
|
|
// Triggered by ChatRoomResizeManager.timer at the end of a chain of resize events
|
|
ChatRoomResizeEventsEnd : function(){
|
|
var TextAreaChatLog = document.getElementById("TextAreaChatLog");
|
|
|
|
if (TextAreaChatLog != null) {
|
|
// Scrolls to the position held before the resize events.
|
|
if (ChatRoomResizeManager.ChatLogScrolledToEnd) ElementScrollToEnd("TextAreaChatLog"); // Prevents drift away from the end of the chat log.
|
|
else TextAreaChatLog.scrollTop = (ChatRoomResizeManager.ChatRoomScrollPercentage * TextAreaChatLog.scrollHeight) - TextAreaChatLog.clientHeight;
|
|
}
|
|
ChatRoomResizeManager.atStart = true;
|
|
},
|
|
};
|
|
|
|
|
|
/**
|
|
* Checks if the player can add the current character to her whitelist.
|
|
* @returns {boolean} - TRUE if the current character is not in the player's whitelist nor blacklist.
|
|
*/
|
|
function ChatRoomCanAddWhiteList() { return ((CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && (Player.WhiteList.indexOf(CurrentCharacter.MemberNumber) < 0) && (Player.BlackList.indexOf(CurrentCharacter.MemberNumber) < 0)); }
|
|
/**
|
|
* Checks if the player can add the current character to her blacklist.
|
|
* @returns {boolean} - TRUE if the current character is not in the player's whitelist nor blacklist.
|
|
*/
|
|
function ChatRoomCanAddBlackList() { return ((CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && (Player.WhiteList.indexOf(CurrentCharacter.MemberNumber) < 0) && (Player.BlackList.indexOf(CurrentCharacter.MemberNumber) < 0)); }
|
|
/**
|
|
* Checks if the player can remove the current character from her whitelist.
|
|
* @returns {boolean} - TRUE if the current character is in the player's whitelist, but not her blacklist.
|
|
*/
|
|
function ChatRoomCanRemoveWhiteList() { return ((CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && (Player.WhiteList.indexOf(CurrentCharacter.MemberNumber) >= 0)); }
|
|
/**
|
|
* Checks if the player can remove the current character from her blacklist.
|
|
* @returns {boolean} - TRUE if the current character is in the player's blacklist, but not her whitelist.
|
|
*/
|
|
function ChatRoomCanRemoveBlackList() { return ((CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && (Player.BlackList.indexOf(CurrentCharacter.MemberNumber) >= 0)); }
|
|
/**
|
|
* Checks if the player can add the current character to her friendlist
|
|
* @returns {boolean} - TRUE if the current character is not in the player's friendlist yet.
|
|
*/
|
|
function ChatRoomCanAddFriend() { return ((CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && (Player.FriendList.indexOf(CurrentCharacter.MemberNumber) < 0)); }
|
|
/**
|
|
* Checks if the player can remove the current character from her friendlist.
|
|
* @returns {boolean} - TRUE if the current character is in the player's friendlist.
|
|
*/
|
|
function ChatRoomCanRemoveFriend() { return ((CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && (Player.FriendList.indexOf(CurrentCharacter.MemberNumber) >= 0)); }
|
|
/**
|
|
* Checks if the player can add the current character to her ghostlist
|
|
* @returns {boolean} - TRUE if the current character is not in the player's ghostlist yet.
|
|
*/
|
|
function ChatRoomCanAddGhost() { return ((CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && (Player.GhostList.indexOf(CurrentCharacter.MemberNumber) < 0)); }
|
|
/**
|
|
* Checks if the player can remove the current character from her ghostlist.
|
|
* @returns {boolean} - TRUE if the current character is in the player's ghostlist.
|
|
*/
|
|
function ChatRoomCanRemoveGhost() { return ((CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && (Player.GhostList.indexOf(CurrentCharacter.MemberNumber) >= 0)); }
|
|
/**
|
|
* Checks if the player can change the current character's clothes
|
|
* @returns {boolean} - TRUE if the player can change the character's clothes and is allowed to.
|
|
*/
|
|
function ChatRoomCanChangeClothes() { return (Player.CanInteract() && (CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && CurrentCharacter.AllowItem && !CurrentCharacter.IsEnclose() && !((InventoryGet(CurrentCharacter, "ItemNeck") != null) && (InventoryGet(CurrentCharacter, "ItemNeck").Asset.Name == "ClubSlaveCollar"))); }
|
|
/**
|
|
* Checks if the specified owner option is available.
|
|
* @param {string} Option - The option to check for availability
|
|
* @returns {boolean} - TRUE if the current ownership option is the specified one.
|
|
*/
|
|
function ChatRoomOwnershipOptionIs(Option) { return (Option == ChatRoomOwnershipOption); }
|
|
/**
|
|
* Checks if the specified lover option is available.
|
|
* @param {string} Option - The option to check for availability
|
|
* @returns {boolean} - TRUE if the current lover option is the specified one.
|
|
*/
|
|
function ChatRoomLovershipOptionIs(Option) { return (Option == ChatRoomLovershipOption); }
|
|
/**
|
|
* Checks if the player can take a drink from the current character's tray.
|
|
* @returns {boolean} - TRUE if the current character is wearing a drinks tray and the player can interact.
|
|
*/
|
|
function ChatRoomCanTakeDrink() { return ((CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && (CurrentCharacter.ID != 0) && Player.CanInteract() && (InventoryGet(CurrentCharacter, "ItemMisc") != null) && (InventoryGet(CurrentCharacter, "ItemMisc").Asset.Name == "WoodenMaidTrayFull")); }
|
|
/**
|
|
* Checks if the current character is owned by the player.
|
|
* @returns {boolean} - TRUE if the current character is owned by the player.
|
|
*/
|
|
function ChatRoomIsCollaredByPlayer() { return ((CurrentCharacter != null) && (CurrentCharacter.Ownership != null) && (CurrentCharacter.Ownership.Stage == 1) && (CurrentCharacter.Ownership.MemberNumber == Player.MemberNumber)); }
|
|
/**
|
|
* Checks if the current character is owned by the player. (Including trial)
|
|
* @returns {boolean} - TRUE if the current character is owned by the player.
|
|
*/
|
|
function ChatRoomIsOwnedByPlayer() { return CurrentCharacter != null && CurrentCharacter.Ownership != null && CurrentCharacter.Ownership.MemberNumber == Player.MemberNumber; }
|
|
/**
|
|
* Checks if the current character is wearing any collar.
|
|
* @returns {boolean} - TRUE if the current character is owned by the player.
|
|
*/
|
|
function ChatRoomIsWearingCollar() { return CurrentCharacter != null && InventoryGet(CurrentCharacter, "ItemNeck") !== null; }
|
|
/**
|
|
* Checks if the current character is lover of the player.
|
|
* @returns {boolean} - TRUE if the current character is lover of the player.
|
|
*/
|
|
function ChatRoomIsLoverOfPlayer() { return ((CurrentCharacter != null) && CurrentCharacter.GetLoversNumbers().includes(Player.MemberNumber)); }
|
|
/**
|
|
* Checks if the current character can serve drinks.
|
|
* @returns {boolean} - TRUE if the character is a maid and is free.
|
|
*/
|
|
function ChatRoomCanServeDrink() { return ((CurrentCharacter != null) && CurrentCharacter.CanWalk() && (ReputationCharacterGet(CurrentCharacter, "Maid") > 0) && CurrentCharacter.CanTalk()); }
|
|
/**
|
|
* Checks if the player can give a money envelope to her owner
|
|
* @returns {boolean} - TRUE if the current character is the owner of the player, and the player has the envelope
|
|
*/
|
|
function ChatRoomCanGiveMoneyForOwner() { return ((ChatRoomMoneyForOwner > 0) && (CurrentCharacter != null) && (Player.Ownership != null) && (Player.Ownership.Stage == 1) && (Player.Ownership.MemberNumber == CurrentCharacter.MemberNumber)); }
|
|
/**
|
|
* Checks if the player is a chatroom admin.
|
|
* @returns {boolean} - TRUE if the player is an admin of the current chatroom.
|
|
*/
|
|
function ChatRoomPlayerIsAdmin() { return ((ChatRoomData != null && ChatRoomData.Admin != null) && (ChatRoomData.Admin.indexOf(Player.MemberNumber) >= 0)); }
|
|
/**
|
|
* Checks if the current character is an admin of the chatroom.
|
|
* @returns {boolean} - TRUE if the current character is an admin.
|
|
*/
|
|
function ChatRoomCurrentCharacterIsAdmin() { return ((CurrentCharacter != null) && (ChatRoomData.Admin != null) && (ChatRoomData.Admin.indexOf(CurrentCharacter.MemberNumber) >= 0)); }
|
|
/**
|
|
* Checks if the room allows the photograph feature to be used.
|
|
* @returns {boolean} - TRUE if the player can take a photo.
|
|
*/
|
|
function ChatRoomCanTakePhotos() { return (ChatRoomData && ChatRoomData.BlockCategory && !ChatRoomData.BlockCategory.includes("Photos")) || !ChatRoomData; }
|
|
|
|
/**
|
|
* Checks if the player can start searching a player
|
|
* @returns {boolean} - Returns TRUE if the player can start searching a player
|
|
*/
|
|
function ChatRoomCanTakeSuitcase() {
|
|
return ChatRoomCarryingBounty(CurrentCharacter) && !CurrentCharacter.CanInteract();
|
|
}
|
|
/**
|
|
* Checks if the player can start searching a player
|
|
* @returns {boolean} - Returns TRUE if the player can start searching a player
|
|
*/
|
|
function ChatRoomCanTakeSuitcaseOpened() {
|
|
return ChatRoomCarryingBountyOpened(CurrentCharacter) && !CurrentCharacter.CanInteract();
|
|
}
|
|
/**
|
|
* Checks if the player can start searching a player
|
|
* @returns {boolean} - Returns TRUE if the player can start searching a player
|
|
*/
|
|
function ChatRoomCarryingBounty(C) {
|
|
return (ReputationGet("Kidnap") > 0 && Player.CanInteract() && C.AllowItem != false && InventoryIsWorn(C,"BountySuitcase", "ItemMisc"));
|
|
}
|
|
/**
|
|
* Checks if the player can start searching a player
|
|
* @returns {boolean} - Returns TRUE if the player can start searching a player
|
|
*/
|
|
function ChatRoomCarryingBountyOpened(C) {
|
|
return (ReputationGet("Kidnap") > 0 && Player.CanInteract() && C.AllowItem != false && InventoryIsWorn(C,"BountySuitcaseEmpty", "ItemMisc"));
|
|
}
|
|
/**
|
|
* Checks if the player can start searching a player but the player is unbound
|
|
* @returns {boolean} - Returns TRUE if the player can start searching a player
|
|
*/
|
|
function ChatRoomCantTakeSuitcase() {
|
|
return (Player.CanInteract() && CurrentCharacter.CanInteract() && CurrentCharacter.AllowItem && InventoryIsWorn(CurrentCharacter,"BountySuitcase", "ItemMisc"));
|
|
}
|
|
/**
|
|
* Checks if the player can start searching a player but the player is unbound
|
|
* @returns {boolean} - Returns TRUE if the player can start searching a player
|
|
*/
|
|
function ChatRoomCantTakeSuitcaseOpened() {
|
|
return (Player.CanInteract() && CurrentCharacter.CanInteract() && CurrentCharacter.AllowItem && InventoryIsWorn(CurrentCharacter,"BountySuitcaseEmpty", "ItemMisc"));
|
|
}
|
|
/**
|
|
* Attempts to take the suitcase from the current player
|
|
* @returns {void}
|
|
*/
|
|
function ChatRoomTryToTakeSuitcase() {
|
|
ServerSend("ChatRoomChat", { Content: "TakeSuitcase", Type: "Hidden", Target: CurrentCharacter.MemberNumber});
|
|
|
|
if (KidnapLeagueOnlineBountyTarget == 0) {
|
|
KidnapLeagueOnlineBountyTargetStartedTime = CommonTime();
|
|
}
|
|
KidnapLeagueOnlineBountyTarget = CurrentCharacter.MemberNumber;
|
|
CurrentCharacter = null;
|
|
}
|
|
/**
|
|
* Receives money from the suitcase
|
|
* @returns {void}
|
|
*/
|
|
function ChatRoomReceiveSuitcaseMoney() {
|
|
let money = Math.max(1, Math.ceil(15 * Math.min(1, Math.max(0, (CommonTime() - KidnapLeagueOnlineBountyTargetStartedTime)/KidnapLeagueSearchFinishDuration))));
|
|
CharacterChangeMoney(Player, money);
|
|
ChatRoomMessage({ Content: "OnlineBountySuitcaseFinish", Type: "Action", Dictionary: [{Tag: "MONEYAMOUNT", Text: ""+Math.ceil(money)}], Sender: Player.MemberNumber });
|
|
KidnapLeagueOnlineBountyTarget = 0;
|
|
KidnapLeagueOnlineBountyTargetStartedTime = 0;
|
|
}
|
|
|
|
/**
|
|
* Checks if the player can give the target character her high security keys.
|
|
* @returns {boolean} - TRUE if the player can interact and is allowed to interact with the current character.
|
|
*/
|
|
function ChatRoomCanGiveHighSecurityKeys() {
|
|
if (Player.Appearance != null)
|
|
for (let A = 0; A < Player.Appearance.length; A++)
|
|
if (Player.Appearance[A].Asset && Player.Appearance[A].Property && InventoryGetLock(Player.Appearance[A]) && InventoryGetLock(Player.Appearance[A]).Asset.ExclusiveUnlock
|
|
&& (Player.Appearance[A].Property.MemberNumberListKeys)
|
|
&& (Player.Appearance[A].Property.MemberNumberListKeys
|
|
&& CommonConvertStringToArray("" + Player.Appearance[A].Property.MemberNumberListKeys).indexOf(Player.MemberNumber) >= 0
|
|
&& CommonConvertStringToArray("" + Player.Appearance[A].Property.MemberNumberListKeys).indexOf(CurrentCharacter.MemberNumber) < 0)) // Make sure you have a lock they dont have the keys to
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if the player can give the target character her high security keys, while also removing the ones from her
|
|
* possession
|
|
* @returns {boolean} - TRUE if the player can interact and is allowed to interact with the current character.
|
|
*/
|
|
function ChatRoomCanGiveHighSecurityKeysAll() {
|
|
if (Player.Appearance != null)
|
|
for (let A = 0; A < Player.Appearance.length; A++)
|
|
if (Player.Appearance[A].Asset && Player.Appearance[A].Property && InventoryGetLock(Player.Appearance[A]) && InventoryGetLock(Player.Appearance[A]).Asset.ExclusiveUnlock
|
|
&& (Player.Appearance[A].Property.MemberNumberListKeys || (!Player.Appearance[A].Property.MemberNumberListKeys && Player.Appearance[A].Property.LockMemberNumber == Player.MemberNumber))
|
|
&& (!Player.Appearance[A].Property.MemberNumberListKeys
|
|
|| (CommonConvertStringToArray("" + Player.Appearance[A].Property.MemberNumberListKeys).indexOf(Player.MemberNumber) >= 0))) // Make sure you have a lock they dont have the keys to
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
function ChatRoomGiveHighSecurityKeys() {
|
|
var C = Player;
|
|
if (C.Appearance != null)
|
|
for (let A = 0; A < C.Appearance.length; A++)
|
|
if (C.Appearance[A].Asset && C.Appearance[A].Property && InventoryGetLock(Player.Appearance[A]) && InventoryGetLock(Player.Appearance[A]).Asset.ExclusiveUnlock
|
|
&& C.Appearance[A].Property.MemberNumberListKeys
|
|
&& CommonConvertStringToArray("" + C.Appearance[A].Property.MemberNumberListKeys).indexOf(Player.MemberNumber) >= 0
|
|
&& CommonConvertStringToArray("" + C.Appearance[A].Property.MemberNumberListKeys).indexOf(CurrentCharacter.MemberNumber) < 0) // Make sure you have a lock they dont have the keys to
|
|
C.Appearance[A].Property.MemberNumberListKeys = C.Appearance[A].Property.MemberNumberListKeys + "," + CurrentCharacter.MemberNumber;
|
|
CharacterRefresh(Player);
|
|
ChatRoomCharacterUpdate(Player);
|
|
}
|
|
function ChatRoomGiveHighSecurityKeysAll() {
|
|
var C = Player;
|
|
if (C.Appearance != null)
|
|
for (let A = 0; A < C.Appearance.length; A++)
|
|
if (C.Appearance[A].Asset && C.Appearance[A].Property && InventoryGetLock(Player.Appearance[A]) && InventoryGetLock(Player.Appearance[A]).Asset.ExclusiveUnlock
|
|
&& (C.Appearance[A].Property.MemberNumberListKeys || (!C.Appearance[A].Property.MemberNumberListKeys && C.Appearance[A].Property.LockMemberNumber == Player.MemberNumber))
|
|
&& (!C.Appearance[A].Property.MemberNumberListKeys || (C.Appearance[A].Property.MemberNumberListKeys
|
|
&& CommonConvertStringToArray("" + C.Appearance[A].Property.MemberNumberListKeys).indexOf(Player.MemberNumber) >= 0))) // Make sure you have a lock they dont have the keys to
|
|
{
|
|
if (C.Appearance[A].Property.MemberNumberListKeys) {
|
|
var list = CommonConvertStringToArray("" + C.Appearance[A].Property.MemberNumberListKeys);
|
|
|
|
if (list) {
|
|
list = list.filter(x => x !== Player.MemberNumber);
|
|
if (list.indexOf(CurrentCharacter.MemberNumber) < 0)
|
|
list.push(CurrentCharacter.MemberNumber);
|
|
C.Appearance[A].Property.MemberNumberListKeys = "" +
|
|
CommonConvertArrayToString(list); // Convert to array and back; can only save strings on server
|
|
}
|
|
}
|
|
C.Appearance[A].Property.LockMemberNumber = CurrentCharacter.MemberNumber;
|
|
}
|
|
CharacterRefresh(Player);
|
|
ChatRoomCharacterUpdate(Player);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Checks if the player can help the current character by giving them a lockpick
|
|
* @returns {boolean} - TRUE if the player can interact and is allowed to interact with the current character.
|
|
*/
|
|
function ChatRoomCanGiveLockpicks() {
|
|
if (Player.CanInteract())
|
|
for (let I = 0; I < Player.Inventory.length; I++)
|
|
if (Player.Inventory[I].Name == "Lockpicks") {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Checks if the player can help the current character by giving her lockpicks
|
|
* @returns {boolean} - TRUE if the player can interact and is allowed to interact with the current character.
|
|
*/
|
|
function ChatRoomCanAssistStruggle() { return CurrentCharacter.AllowItem && !CurrentCharacter.CanInteract(); }
|
|
/**
|
|
* Checks if the character options menu is available.
|
|
* @returns {boolean} - Whether or not the player can interact with the target character
|
|
*/
|
|
function ChatRoomCanPerformCharacterAction() {
|
|
return ChatRoomCanAssistStand() || ChatRoomCanAssistKneel() || ChatRoomCanAssistStruggle() || ChatRoomCanHoldLeash() || ChatRoomCanStopHoldLeash()
|
|
|| ChatRoomCanTakePhotos() || ChatRoomCanGiveLockpicks() || ChatRoomCanGiveHighSecurityKeys() || ChatRoomCanGiveHighSecurityKeysAll();
|
|
}
|
|
/**
|
|
* Checks if the target character can be helped back on her feet. This is different than CurrentCharacter.CanKneel()
|
|
* because it listens for the current active pose and removes certain checks that are not required for someone else to
|
|
* help a person kneel down.
|
|
* @returns {boolean} - Whether or not the target character can stand
|
|
*/
|
|
function ChatRoomCanAssistStand() {
|
|
return Player.CanInteract() && CurrentCharacter.AllowItem && CharacterItemsHavePoseAvailable(CurrentCharacter, "BodyLower", "Kneel") && !CharacterDoItemsSetPose(CurrentCharacter, "Kneel") && CurrentCharacter.IsKneeling();
|
|
}
|
|
/**
|
|
* Checks if the target character can be helped down on her knees. This is different than CurrentCharacter.CanKneel()
|
|
* because it listens for the current active pose and removes certain checks that are not required for someone else to
|
|
* help a person kneel down.
|
|
* @returns {boolean} - Whether or not the target character can stand
|
|
*/
|
|
|
|
function ChatRoomCanAssistKneel() {
|
|
return Player.CanInteract() && CurrentCharacter.AllowItem && CharacterItemsHavePoseAvailable(CurrentCharacter, "BodyLower", "Kneel") && !CharacterDoItemsSetPose(CurrentCharacter, "Kneel") && !CurrentCharacter.IsKneeling();
|
|
}
|
|
|
|
/**
|
|
* Checks if the player character can attempt to stand up. This is different than CurrentCharacter.CanKneel() because it
|
|
* listens for the current active pose, but it forces the player to do a minigame.
|
|
* @returns {boolean} - Whether or not the player character can stand
|
|
*/
|
|
function ChatRoomCanAttemptStand() { return CharacterItemsHavePoseAvailable(Player, "BodyLower", "Kneel") && !CharacterDoItemsSetPose(Player, "Kneel") && Player.IsKneeling();}
|
|
/**
|
|
* Checks if the player character can attempt to get down on her knees. This is different than
|
|
* CurrentCharacter.CanKneel() because it listens for the current active pose, but it forces the player to do a
|
|
* minigame.
|
|
* @returns {boolean} - Whether or not the player character can stand
|
|
*/
|
|
function ChatRoomCanAttemptKneel() { return CharacterItemsHavePoseAvailable(Player, "BodyLower", "Kneel") && !CharacterDoItemsSetPose(Player, "Kneel") && !Player.IsKneeling(); }
|
|
|
|
/**
|
|
* Checks if the player can stop the current character from leaving.
|
|
* @returns {boolean} - TRUE if the current character is slowed down and can be interacted with.
|
|
*/
|
|
function ChatRoomCanStopSlowPlayer() { return (CurrentCharacter.IsSlow() && Player.CanInteract() && CurrentCharacter.AllowItem ); }
|
|
/**
|
|
* Checks if the player can grab the targeted player's leash
|
|
* @returns {boolean} - TRUE if the player can interact and is allowed to interact with the current character.
|
|
*/
|
|
function ChatRoomCanHoldLeash() { return CurrentCharacter.AllowItem && Player.CanInteract() && CurrentCharacter.OnlineSharedSettings && CurrentCharacter.OnlineSharedSettings.AllowPlayerLeashing != false && ChatRoomLeashList.indexOf(CurrentCharacter.MemberNumber) < 0
|
|
&& ChatRoomCanBeLeashed(CurrentCharacter);}
|
|
/**
|
|
* Checks if the player can let go of the targeted player's leash
|
|
* @returns {boolean} - TRUE if the player can interact and is allowed to interact with the current character.
|
|
*/
|
|
function ChatRoomCanStopHoldLeash() {
|
|
if (CurrentCharacter.AllowItem && Player.CanInteract() && CurrentCharacter.OnlineSharedSettings && CurrentCharacter.OnlineSharedSettings.AllowPlayerLeashing != false && ChatRoomLeashList.indexOf(CurrentCharacter.MemberNumber) >= 0) {
|
|
if (ChatRoomCanBeLeashed(CurrentCharacter)) {
|
|
return true;
|
|
} else {
|
|
ChatRoomLeashList.splice(ChatRoomLeashList.indexOf(CurrentCharacter.MemberNumber), 1);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Checks if the targeted player is a valid leash target
|
|
* @returns {boolean} - TRUE if the player can be leashed
|
|
*/
|
|
function ChatRoomCanBeLeashed(C) {
|
|
return ChatRoomCanBeLeashedBy(Player.MemberNumber, C);
|
|
}
|
|
|
|
/**
|
|
* Checks if the targeted player is a valid leash target for the source member number
|
|
* @param {number} sourceMemberNumber - Member number of the source player
|
|
* @param {Character} C - Target player
|
|
* @returns {boolean} - TRUE if the player can be leashed
|
|
*/
|
|
function ChatRoomCanBeLeashedBy(sourceMemberNumber, C) {
|
|
if ((ChatRoomData && ChatRoomData.BlockCategory.indexOf("Leashing") < 0) || !ChatRoomData) {
|
|
// Have to not be tethered, and need a leash
|
|
var canLeash = false;
|
|
var isTrapped = false;
|
|
var neckLock = null;
|
|
for (let A = 0; A < C.Appearance.length; A++)
|
|
if ((C.Appearance[A].Asset != null) && (C.Appearance[A].Asset.Group.Family == C.AssetFamily)) {
|
|
if (InventoryItemHasEffect(C.Appearance[A], "Leash", true)) {
|
|
canLeash = true;
|
|
if (C.Appearance[A].Asset.Group.Name == "ItemNeckRestraints")
|
|
neckLock = InventoryGetLock(C.Appearance[A]);
|
|
} else if (InventoryItemHasEffect(C.Appearance[A], "Tethered", true) || InventoryItemHasEffect(C.Appearance[A], "Mounted", true) || InventoryItemHasEffect(C.Appearance[A], "Enclose", true) || InventoryItemHasEffect(C.Appearance[A], "OneWayEnclose", true)){
|
|
isTrapped = true;
|
|
}
|
|
}
|
|
|
|
if (canLeash && !isTrapped) {
|
|
if (sourceMemberNumber == 0 || !neckLock || (!neckLock.Asset.OwnerOnly && !neckLock.Asset.LoverOnly) ||
|
|
(neckLock.Asset.OwnerOnly && C.IsOwnedByMemberNumber(sourceMemberNumber)) ||
|
|
(neckLock.Asset.LoverOnly && C.IsLoverOfMemberNumber(sourceMemberNumber))) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if the player has waited long enough to be able to call the maids
|
|
* @returns {boolean} - TRUE if the current character has been in the last chat room for more than 30 minutes
|
|
*/
|
|
function DialogCanCallMaids() { return (CurrentScreen == "ChatRoom" && (ChatRoomData && ChatRoomData.Game == "" && !(LogValue("Committed", "Asylum") >= CurrentTime)) && !Player.CanWalk()) && !MainHallIsMaidsDisabled();}
|
|
|
|
|
|
/**
|
|
* Checks if the player has waited long enough to be able to call the maids
|
|
* @returns {boolean} - TRUE if the current character has been in the last chat room for more than 30 minutes
|
|
*/
|
|
function DialogCanCallMaidsPunishmentOn() { return (DialogCanCallMaids() && (!Player.RestrictionSettings || !Player.RestrictionSettings.BypassNPCPunishments));}
|
|
|
|
|
|
/**
|
|
* Checks if the player has waited long enough to be able to call the maids
|
|
* @returns {boolean} - TRUE if the current character has been in the last chat room for more than 30 minutes
|
|
*/
|
|
function DialogCanCallMaidsPunishmentOff() { return (DialogCanCallMaids() && Player.RestrictionSettings && Player.RestrictionSettings.BypassNPCPunishments);}
|
|
|
|
/**
|
|
* Creates the chat room input elements.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomCreateElement() {
|
|
// Creates the chat box
|
|
if (document.getElementById("InputChat") == null) {
|
|
ElementCreateTextArea("InputChat");
|
|
document.getElementById("InputChat").setAttribute("maxLength", 1000);
|
|
document.getElementById("InputChat").setAttribute("autocomplete", "off");
|
|
ElementFocus("InputChat");
|
|
} else if (document.getElementById("InputChat").style.display == "none") ElementFocus("InputChat");
|
|
|
|
// Creates the chat log
|
|
if (document.getElementById("TextAreaChatLog") == null) {
|
|
|
|
// Sets the size and position
|
|
ElementCreateDiv("TextAreaChatLog");
|
|
ElementPositionFix("TextAreaChatLog", ChatRoomFontSize, 1005, 5, 988, 859);
|
|
ElementScrollToEnd("TextAreaChatLog");
|
|
ChatRoomRefreshChatSettings();
|
|
|
|
// If we relog, we reload the previous chat log
|
|
if (RelogChatLog != null) {
|
|
while (RelogChatLog.children.length > 0)
|
|
document.getElementById("TextAreaChatLog").appendChild(RelogChatLog.children[0]);
|
|
ElementValue("InputChat", RelogInputText);
|
|
RelogChatLog = null;
|
|
RelogInputText = "";
|
|
} else ElementContent("TextAreaChatLog", "");
|
|
|
|
// Creates listener for resize events.
|
|
window.addEventListener("resize", ChatRoomResizeManager.ChatRoomResizeEvent);
|
|
|
|
} else if (document.getElementById("TextAreaChatLog").style.display == "none") {
|
|
setTimeout(() => ElementScrollToEnd("TextAreaChatLog"), 100);
|
|
ChatRoomRefreshChatSettings();
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Loads the chat room screen by displaying the proper inputs.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomLoad() {
|
|
ElementRemove("InputSearch");
|
|
ElementRemove("InputName");
|
|
ElementRemove("InputDescription");
|
|
ElementRemove("InputSize");
|
|
ChatRoomRefreshFontSize();
|
|
ChatRoomCreateElement();
|
|
ChatRoomCharacterUpdate(Player);
|
|
ActivityChatRoomArousalSync(Player);
|
|
if (!ChatRoomData || ChatRoomData.Name !== Player.LastChatRoom) {
|
|
ChatRoomHideIconState = 0;
|
|
}
|
|
ChatRoomMenuBuild();
|
|
|
|
ChatRoomCharacterInitialize = true;
|
|
|
|
TextPrefetch("Character", "FriendList");
|
|
TextPrefetch("Online", "ChatAdmin");
|
|
}
|
|
|
|
/**
|
|
* Removes all elements that can be open in the chat room
|
|
*/
|
|
function ChatRoomClearAllElements() {
|
|
// Dialog
|
|
DialogLeave();
|
|
|
|
// Friendlist
|
|
ElementRemove("FriendList");
|
|
FriendListBeepMenuClose();
|
|
FriendListModeIndex = 0;
|
|
|
|
// Admin
|
|
ElementRemove("InputName");
|
|
ElementRemove("InputDescription");
|
|
ElementRemove("InputSize");
|
|
ElementRemove("InputAdminList");
|
|
ElementRemove("InputBanList");
|
|
ElementRemove("InputBackground");
|
|
ElementRemove("TagDropDown");
|
|
|
|
// Chatroom
|
|
ElementRemove("InputChat");
|
|
ElementRemove("TextAreaChatLog");
|
|
|
|
// Preferences
|
|
ElementRemove("InputEmailOld");
|
|
ElementRemove("InputEmailNew");
|
|
ElementRemove("InputCharacterLabelColor");
|
|
PreferenceSubscreen = "";
|
|
|
|
// Profile
|
|
ElementRemove("DescriptionInput");
|
|
|
|
// Wardrobe
|
|
ElementRemove("InputWardrobeName");
|
|
CharacterAppearanceMode = "";
|
|
|
|
// Listeners
|
|
window.removeEventListener("resize", ChatRoomResizeManager.ChatRoomResizeEvent);
|
|
}
|
|
|
|
/**
|
|
* Starts the chatroom selection screen.
|
|
* @param {string} Space - Name of the chatroom space
|
|
* @param {string} Game - Name of the chatroom game to play
|
|
* @param {string} LeaveRoom - Name of the room to go too when exiting chatsearch.
|
|
* @param {string} Background - Name of the background to use in chatsearch.
|
|
* @param {Array} BackgroundTagList - List of available backgrounds in the chatroom space.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomStart(Space, Game, LeaveRoom, Background, BackgroundTagList) {
|
|
ChatRoomSpace = Space;
|
|
ChatRoomGame = Game;
|
|
ChatSearchLeaveRoom = LeaveRoom;
|
|
ChatSearchBackground = Background;
|
|
ChatCreateBackgroundList = BackgroundsGenerateList(BackgroundTagList);
|
|
BackgroundSelectionTagList = BackgroundTagList;
|
|
CommonSetScreen("Online", "ChatSearch");
|
|
|
|
}
|
|
|
|
/**
|
|
* Create the list of chat room menu buttons
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomMenuBuild() {
|
|
ChatRoomMenuButtons = [];
|
|
ChatRoomMenuButtons.push("Exit");
|
|
if (ChatRoomGame === "") ChatRoomMenuButtons.push("Cut");
|
|
else ChatRoomMenuButtons.push("GameOption");
|
|
ChatRoomMenuButtons.push("Kneel", "Icons");
|
|
if (ChatRoomCanTakePhotos()) ChatRoomMenuButtons.push("Camera");
|
|
ChatRoomMenuButtons.push("Dress", "Profile", "Admin");
|
|
}
|
|
|
|
/**
|
|
* Checks if the player's owner is inside the chatroom.
|
|
* @returns {boolean} - Returns TRUE if the player's owner is inside the room.
|
|
*/
|
|
function ChatRoomOwnerInside() {
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++)
|
|
if (Player.Ownership.MemberNumber == ChatRoomCharacter[C].MemberNumber)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates the temporary drawing arrays for characters, to handle things that are dependent on the drawn chat room
|
|
* characters rather than the ones actually present
|
|
* @returns {void} - Nothing
|
|
*/
|
|
|
|
function ChatRoomUpdateDisplay() {
|
|
// The number of characters to show in the room
|
|
const RenderSingle = Player.GameplaySettings.SensDepChatLog == "SensDepExtreme" && Player.GetBlindLevel() >= 3 && !Player.Effect.includes("VRAvatars");
|
|
ChatRoomCharacterDrawlist = ChatRoomCharacter;
|
|
ChatRoomSenseDepBypass = false;
|
|
if (Player.Effect.includes("VRAvatars")) {
|
|
ChatRoomCharacterDrawlist = [];
|
|
ChatRoomSenseDepBypass = true;
|
|
for (let CC = 0; CC < ChatRoomCharacter.length; CC++) {
|
|
if (ChatRoomCharacter[CC].Effect.includes("VRAvatars")) {
|
|
ChatRoomCharacterDrawlist.push(ChatRoomCharacter[CC]);
|
|
}
|
|
}
|
|
} else if (Player.GetBlindLevel() > 0 && Player.GetBlindLevel() < 3 && Player.ImmersionSettings.BlindAdjacent) {
|
|
// We hide all players except those who are adjacent
|
|
ChatRoomCharacterDrawlist = [];
|
|
ChatRoomSenseDepBypass = true;
|
|
let PlayerIndex = -1;
|
|
// First find the player index
|
|
for (let CC = 0; CC < ChatRoomCharacter.length; CC++) {
|
|
if (ChatRoomCharacter[CC].ID == 0) {
|
|
PlayerIndex = CC;
|
|
break;
|
|
}
|
|
}
|
|
// Then filter the characters
|
|
for (let CC = 0; CC < ChatRoomCharacter.length; CC++) {
|
|
if (Math.abs(CC - PlayerIndex) <= 1) {
|
|
ChatRoomCharacterDrawlist.push(ChatRoomCharacter[CC]);
|
|
}
|
|
}
|
|
}
|
|
|
|
ChatRoomCharacterCount = RenderSingle ? 1 : ChatRoomCharacterDrawlist.length;
|
|
}
|
|
|
|
/**
|
|
* Draws the chatroom characters.
|
|
* @param {boolean} DoClick - Whether or not a click was registered.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomDrawCharacter(DoClick) {
|
|
// Intercepts the online game chat room clicks if we need to
|
|
if (DoClick && OnlineGameClick()) return;
|
|
|
|
// The darkness factors varies with blindness level (1 is bright, 0 is pitch black)
|
|
let DarkFactor = CharacterGetDarkFactor(Player);
|
|
|
|
// Check if we should use a custom background
|
|
const CustomBG = !DoClick ? DrawGetCustomBackground() : "";
|
|
const Background = CustomBG || ChatRoomData.Background;
|
|
if (CustomBG && (DarkFactor === 0.0 || Player.GameplaySettings.SensDepChatLog == "SensDepLight")) DarkFactor = CharacterGetDarkFactor(Player, true);
|
|
|
|
// Determine the horizontal & vertical position and zoom levels to fit all characters evenly in the room
|
|
const Space = ChatRoomCharacterCount >= 2 ? 1000 / Math.min(ChatRoomCharacterCount, 5) : 500;
|
|
|
|
// Gradually slide the characters around to make room
|
|
let weight = ChatRoomSlideWeight;
|
|
if (ChatRoomCharacterInitialize || !(Player.GraphicsSettings && Player.GraphicsSettings.SmoothZoom)) {
|
|
ChatRoomCharacterInitialize = false;
|
|
weight = 0;
|
|
}
|
|
ChatRoomCharacterZoom = (ChatRoomCharacterZoom * weight + ((ChatRoomCharacterCount >= 3 ? Space / 400 : 1))) / (weight + 1);
|
|
ChatRoomCharacterX_Upper = (ChatRoomCharacterX_Upper * weight + 500 - 0.5 * Space * Math.min(ChatRoomCharacterCount, 5))/(weight + 1);
|
|
ChatRoomCharacterX_Lower = (ChatRoomCharacterX_Lower * weight + 500 - 0.5 * Space * Math.max(1, ChatRoomCharacterCount - 5))/(weight + 1);
|
|
|
|
const Zoom = ChatRoomCharacterZoom;
|
|
const X = ChatRoomCharacterCount >= 3 ? (Space - 500 * Zoom) / 2 : 0;
|
|
const Y = ChatRoomCharacterCount <= 5 ? 1000 * (1 - Zoom) / 2 : 0;
|
|
const InvertRoom = Player.GraphicsSettings.InvertRoom && Player.IsInverted();
|
|
|
|
// Draw the background
|
|
if (!DoClick) {
|
|
ChatRoomDrawBackground(Background, Y, Zoom, DarkFactor, InvertRoom);
|
|
}
|
|
|
|
// Draw the characters (in click mode, we can open the character menu or start whispering to them)
|
|
for (let C = 0; C < ChatRoomCharacterDrawlist.length; C++) {
|
|
let ChatRoomCharacterX = C >= 5 ? ChatRoomCharacterX_Lower : ChatRoomCharacterX_Upper;
|
|
if (!(Player.GraphicsSettings && Player.GraphicsSettings.CenterChatrooms)) ChatRoomCharacterX = 0;
|
|
|
|
const CharX = ChatRoomCharacterX + (ChatRoomCharacterCount == 1 ? 0 : X + (C % 5) * Space);
|
|
const CharY = ChatRoomCharacterCount == 1 ? 0 : Y + Math.floor(C / 5) * 500;
|
|
if (ChatRoomCharacterCount == 1 && ChatRoomCharacterDrawlist[C].ID !== 0) { // Only render the player!
|
|
continue;
|
|
}
|
|
if (DoClick) {
|
|
if (MouseIn(CharX, CharY, Space, 1000 * Zoom)) {
|
|
return ChatRoomClickCharacter(ChatRoomCharacterDrawlist[C], CharX, CharY, Zoom, (MouseX - CharX) / Zoom, (MouseY - CharY) / Zoom, C);
|
|
}
|
|
} else {
|
|
// Draw the background a second time for characters 6 to 10 (we do it here to correct clipping errors from the first part)
|
|
if (C === 5) {
|
|
ChatRoomDrawBackground(Background, 500, Zoom, DarkFactor, InvertRoom);
|
|
}
|
|
|
|
// Draw the character
|
|
DrawCharacter(ChatRoomCharacterDrawlist[C], CharX, CharY, Zoom);
|
|
|
|
// Draw the character overlay
|
|
if (ChatRoomCharacterDrawlist[C].MemberNumber != null) {
|
|
ChatRoomDrawCharacterOverlay(ChatRoomCharacterDrawlist[C], CharX, CharY, Zoom, C);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draw the background of a chat room
|
|
* @param {string} Background - The name of the background image file
|
|
* @param {number} Y - The starting Y co-ordinate of the image
|
|
* @param {number} Zoom - The zoom factor based on the number of characters
|
|
* @param {number} DarkFactor - The value (0 = fully visible, 1 = black) to tint the background
|
|
* @param {boolean} InvertRoom - Whether the background image should be inverted
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomDrawBackground(Background, Y, Zoom, DarkFactor, InvertRoom) {
|
|
if (DarkFactor > 0) {
|
|
// Draw the zoomed background
|
|
DrawImageZoomCanvas("Backgrounds/" + Background + ".jpg", MainCanvas, 500 * (2 - 1 / Zoom), 0, 1000 / Zoom, 1000, 0, Y, 1000, 1000 * Zoom, InvertRoom);
|
|
|
|
// Draw an overlay if the character is partially blinded
|
|
if (DarkFactor < 1.0) {
|
|
DrawRect(0, Y, 1000, 1000 - Y, "rgba(0,0,0," + (1.0 - DarkFactor) + ")");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws any overlays on top of character
|
|
* @param {Character} C The target character
|
|
* @param {number} CharX Character's X position on canvas
|
|
* @param {number} CharY Character's Y position on canvas
|
|
* @param {number} Zoom Room zoom
|
|
* @param {number} Pos Index of target character
|
|
*/
|
|
function ChatRoomDrawCharacterOverlay(C, CharX, CharY, Zoom, Pos) {
|
|
// Draw the ghostlist/friendlist, whitelist/blacklist, admin icons
|
|
if (ChatRoomHideIconState == 0) {
|
|
if (Player.WhiteList.includes(C.MemberNumber)) {
|
|
DrawImageResize("Icons/Small/WhiteList.png", CharX + 75 * Zoom, CharY, 50 * Zoom, 50 * Zoom);
|
|
} else if (Player.BlackList.includes(C.MemberNumber)) {
|
|
DrawImageResize("Icons/Small/BlackList.png", CharX + 75 * Zoom, CharY, 50 * Zoom, 50 * Zoom);
|
|
}
|
|
if (Array.isArray(ChatRoomData.Admin) && ChatRoomData.Admin.includes(C.MemberNumber)) {
|
|
DrawImageResize("Icons/Small/Admin.png", CharX + 125 * Zoom, CharY, 50 * Zoom, 50 * Zoom);
|
|
}
|
|
if (ChatRoomCarryingBounty(C)) {
|
|
DrawImageResize("Icons/Small/Money.png", CharX + 225 * Zoom, CharY, 50 * Zoom, 50 * Zoom);
|
|
}
|
|
// Warning icon when game versions don't match
|
|
if (C.OnlineSharedSettings && C.OnlineSharedSettings.GameVersion !== GameVersion) {
|
|
DrawImageResize("Icons/Small/Warning.png", CharX + 325 * Zoom, CharY, 50 * Zoom, 50 * Zoom);
|
|
}
|
|
if (Player.GhostList.includes(C.MemberNumber)) {
|
|
DrawImageResize("Icons/Small/GhostList.png", CharX + 375 * Zoom, CharY, 50 * Zoom, 50 * Zoom);
|
|
} else if (Player.FriendList.includes(C.MemberNumber)) {
|
|
DrawImageResize("Icons/Small/FriendList.png", CharX + 375 * Zoom, CharY, 50 * Zoom, 50 * Zoom);
|
|
}
|
|
}
|
|
|
|
if (ChatRoomTargetMemberNumber == C.MemberNumber && ChatRoomHideIconState <= 1) {
|
|
DrawImage("Icons/Small/Whisper.png", CharX + 75 * Zoom, CharY + 950 * Zoom);
|
|
}
|
|
|
|
if (ChatRoomMoveTarget !== null) {
|
|
const MoveTargetPos = ChatRoomCharacter.findIndex(c => c.MemberNumber === ChatRoomMoveTarget);
|
|
if (MoveTargetPos < 0) {
|
|
ChatRoomMoveTarget = null;
|
|
} else {
|
|
if (ChatRoomMoveTarget === C.MemberNumber) {
|
|
DrawButton(CharX + 200 * Zoom, CharY + 750 * Zoom, 90 * Zoom, 90 * Zoom, "", "White");
|
|
DrawImageResize("Icons/Remove.png", CharX + 202 * Zoom, CharY + 752 * Zoom, 86 * Zoom, 86 * Zoom);
|
|
} else {
|
|
if (Pos < MoveTargetPos) {
|
|
DrawButton(CharX + 100 * Zoom, CharY + 750 * Zoom, 90 * Zoom, 90 * Zoom, "", "White");
|
|
DrawImageResize("Icons/Here.png", CharX + 102 * Zoom, CharY + 752 * Zoom, 86 * Zoom, 86 * Zoom);
|
|
}
|
|
DrawButton(CharX + 200 * Zoom, CharY + 750 * Zoom, 90 * Zoom, 90 * Zoom, "", "White");
|
|
DrawImageResize("Icons/Swap.png", CharX + 202 * Zoom, CharY + 752 * Zoom, 86 * Zoom, 86 * Zoom);
|
|
if (Pos > MoveTargetPos) {
|
|
DrawButton(CharX + 300 * Zoom, CharY + 750 * Zoom, 90 * Zoom, 90 * Zoom, "", "White");
|
|
DrawImageResize("Icons/Here.png", CharX + 302 * Zoom, CharY + 752 * Zoom, 86 * Zoom, 86 * Zoom);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when character is clicked
|
|
* @param {Character} C The target character
|
|
* @param {number} CharX Character's X position on canvas
|
|
* @param {number} CharY Character's Y position on canvas
|
|
* @param {number} Zoom Room zoom
|
|
* @param {number} ClickX Click X postion relative to character, without zoom
|
|
* @param {number} ClickY Click Y postion relative to character, without zoom
|
|
* @param {number} Pos Index of target character
|
|
*/
|
|
function ChatRoomClickCharacter(C, CharX, CharY, Zoom, ClickX, ClickY, Pos) {
|
|
|
|
// Click on name
|
|
if (ClickY > 900) {
|
|
// Clicking on self or current target removes whisper target
|
|
if (C.ID === 0 || ChatRoomTargetMemberNumber === C.MemberNumber) {
|
|
ChatRoomSetTarget(null);
|
|
return;
|
|
}
|
|
// BlockWhisper rule, if owner is in chatroom
|
|
if (LogQuery("BlockWhisper", "OwnerRule") && Player.Ownership && Player.Ownership.Stage === 1 && Player.Ownership.MemberNumber !== C.MemberNumber && ChatRoomOwnerInside()) {
|
|
return;
|
|
}
|
|
// Sensory deprivation setting: Total (no whispers) blocks whispers while blind unless both players are in the virtual realm. Then they can text each other.
|
|
if (Player.GameplaySettings.SensDepChatLog === "SensDepExtreme" && Player.GetBlindLevel() >= 3 && !(Player.Effect.includes("VRAvatars") && C.Effect.includes("VRAvatars"))) {
|
|
return;
|
|
}
|
|
ChatRoomSetTarget(C.MemberNumber);
|
|
return;
|
|
}
|
|
|
|
// Moving character inside room
|
|
if (ChatRoomMoveTarget !== null) {
|
|
const MoveTargetPos = ChatRoomCharacter.findIndex(c => c.MemberNumber === ChatRoomMoveTarget);
|
|
if (MoveTargetPos < 0) {
|
|
ChatRoomMoveTarget = null;
|
|
} else {
|
|
if (Pos < MoveTargetPos && MouseIn(CharX + 100 * Zoom, CharY + 750 * Zoom, 90 * Zoom, 90 * Zoom)) {
|
|
// Move left
|
|
for (let i = 0; i < MoveTargetPos - Pos; i++) {
|
|
ServerSend("ChatRoomAdmin", {
|
|
MemberNumber: ChatRoomMoveTarget,
|
|
Action: "MoveLeft",
|
|
Publish: i === 0
|
|
});
|
|
}
|
|
ChatRoomMoveTarget = null;
|
|
} else if (MouseIn(CharX + 200 * Zoom, CharY + 750 * Zoom, 90 * Zoom, 90 * Zoom)) {
|
|
// Swap or cancel
|
|
if (ChatRoomMoveTarget !== C.MemberNumber) {
|
|
ServerSend("ChatRoomAdmin", {
|
|
MemberNumber: Player.ID,
|
|
TargetMemberNumber: ChatRoomMoveTarget,
|
|
DestinationMemberNumber: C.MemberNumber,
|
|
Action: "Swap"
|
|
});
|
|
}
|
|
ChatRoomMoveTarget = null;
|
|
} else if ( Pos > MoveTargetPos && MouseIn(CharX + 300 * Zoom, CharY + 750 * Zoom, 90 * Zoom, 90 * Zoom)) {
|
|
// Move right
|
|
for (let i = 0; i < Pos - MoveTargetPos; i++) {
|
|
ServerSend("ChatRoomAdmin", {
|
|
MemberNumber: ChatRoomMoveTarget,
|
|
Action: "MoveRight",
|
|
Publish: i === 0
|
|
});
|
|
}
|
|
ChatRoomMoveTarget = null;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Disable examining when blind setting. If both players are in the virtual realm, then they can examine each other.
|
|
if (Player.GameplaySettings.BlindDisableExamine && !(Player.Effect.includes("VRAvatars") && C.Effect.includes("VRAvatars")) && C.ID !== 0 && Player.GetBlindLevel() >= 3) {
|
|
return;
|
|
}
|
|
|
|
// If the arousal meter is shown for that character, we can interact with it
|
|
if (C.ArousalSettings && ["Manual", "Hybrid", "Automatic"].includes(C.ArousalSettings.Active)) {
|
|
let MeterShow = C.ID === 0;
|
|
if (C.ID !== 0 && Player.ArousalSettings.ShowOtherMeter && C.ArousalSettings) {
|
|
if (C.ArousalSettings.Visible === "Access") {
|
|
MeterShow = C.AllowItem;
|
|
} else if (C.ArousalSettings.Visible === "All") {
|
|
MeterShow = true;
|
|
}
|
|
}
|
|
if (MeterShow) {
|
|
// The arousal meter can be maximized or minimized by clicking on it
|
|
if (MouseIn(CharX + 60 * Zoom, CharY + 400 * Zoom, 80 * Zoom, 100 * Zoom) && !C.ArousalZoom) { C.ArousalZoom = true; return; }
|
|
if (MouseIn(CharX + 50 * Zoom, CharY + 615 * Zoom, 100 * Zoom, 85 * Zoom) && C.ArousalZoom) { C.ArousalZoom = false; return; }
|
|
|
|
// If the player can manually control her arousal, we set the progress manual and change the facial expression, it can trigger an orgasm at 100%
|
|
if (C.ID === 0 && MouseIn(CharX + 50 * Zoom, CharY + 200 * Zoom, 100 * Zoom, 500 * Zoom) && C.ArousalZoom) {
|
|
if (Player.ArousalSettings.Active === "Manual" || Player.ArousalSettings.Active === "Hybrid") {
|
|
var Arousal = Math.round((CharY + 625 * Zoom - MouseY) / (4 * Zoom));
|
|
ActivitySetArousal(Player, Arousal);
|
|
if (Player.ArousalSettings.AffectExpression) ActivityExpression(Player, Player.ArousalSettings.Progress);
|
|
if (Player.ArousalSettings.Progress == 100) ActivityOrgasmPrepare(Player);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Don't do anything if the thermometer is clicked without access to it
|
|
if (MouseIn(CharX + 50 * Zoom, CharY + 200 * Zoom, 100 * Zoom, 415 * Zoom) && C.ArousalZoom) return;
|
|
}
|
|
}
|
|
|
|
// Intercepts the online game character clicks if we need to
|
|
if (OnlineGameClickCharacter(C)) return;
|
|
|
|
// Gives focus to the character
|
|
document.getElementById("InputChat").style.display = "none";
|
|
document.getElementById("TextAreaChatLog").style.display = "none";
|
|
ChatRoomChatHidden = true;
|
|
ChatRoomBackground = ChatRoomData.Background;
|
|
C.AllowItem = C.ID === 0 || ServerChatRoomGetAllowItem(Player, C);
|
|
ChatRoomOwnershipOption = "";
|
|
ChatRoomLovershipOption = "";
|
|
if (C.ID !== 0) ServerSend("ChatRoomAllowItem", { MemberNumber: C.MemberNumber });
|
|
if (C.IsOwnedByPlayer() || C.IsLoverOfPlayer()) ServerSend("ChatRoomChat", { Content: "RuleInfoGet", Type: "Hidden", Target: C.MemberNumber });
|
|
CharacterSetCurrent(C);
|
|
}
|
|
|
|
/**
|
|
* Sends the request to the server to check the current character's relationship status.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomCheckRelationships() {
|
|
var C = (Player.FocusGroup != null) ? Player : CurrentCharacter;
|
|
if (C.ID != 0) ServerSend("AccountOwnership", { MemberNumber: C.MemberNumber });
|
|
if (C.ID != 0) ServerSend("AccountLovership", { MemberNumber: C.MemberNumber });
|
|
}
|
|
|
|
/**
|
|
* Displays /help content to the player if it's their first visit to a chatroom this session
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomFirstTimeHelp() {
|
|
if (!ChatRoomHelpSeen) {
|
|
if (!Player.ChatSettings || Player.ChatSettings.ShowChatHelp)
|
|
ChatRoomMessage({ Content: "ChatRoomHelp", Type: "Action", Sender: Player.MemberNumber });
|
|
ChatRoomHelpSeen = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the current whisper target and flags a target update
|
|
* @param {number} MemberNumber - The target member number to set
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomSetTarget(MemberNumber) {
|
|
if (MemberNumber !== ChatRoomTargetMemberNumber) {
|
|
ChatRoomTargetMemberNumber = MemberNumber;
|
|
ChatRoomTargetDirty = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the chat input's placeholder text to reflect the current whisper target
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomTarget() {
|
|
// If the target member number hasn't changed, do nothing
|
|
if (!ChatRoomTargetDirty) return;
|
|
|
|
var TargetName = null;
|
|
if (ChatRoomTargetMemberNumber != null) {
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++)
|
|
if (ChatRoomTargetMemberNumber == ChatRoomCharacter[C].MemberNumber) {
|
|
TargetName = ChatRoomCharacter[C].Name;
|
|
break;
|
|
}
|
|
if (TargetName == null) ChatRoomSetTarget(null);
|
|
}
|
|
let placeholder;
|
|
if (ChatRoomTargetMemberNumber != null) {
|
|
placeholder = TextGet("WhisperTo");
|
|
placeholder += " " + TargetName;
|
|
} else {
|
|
placeholder = TextGet("PublicChat");
|
|
}
|
|
document.getElementById("InputChat").placeholder = placeholder;
|
|
}
|
|
|
|
/**
|
|
* Updates the account to set the last chat room
|
|
* @param {string} room - room to set it to. "" to reset.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomSetLastChatRoom(room) {
|
|
if (room != "") {
|
|
if (!ChatRoomNewRoomToUpdate) {
|
|
if (ChatRoomData && ChatRoomData.Background)
|
|
Player.LastChatRoomBG = ChatRoomData.Background;
|
|
if (ChatRoomData && ChatRoomData.Private)
|
|
Player.LastChatRoomPrivate = ChatRoomData.Private;
|
|
if (ChatRoomData && ChatRoomData.Limit)
|
|
Player.LastChatRoomSize = ChatRoomData.Limit;
|
|
if (ChatRoomData && ChatRoomData.Description != null)
|
|
Player.LastChatRoomDesc = ChatRoomData.Description;
|
|
if (ChatRoomData && ChatRoomData.Admin)
|
|
Player.LastChatRoomAdmin = ChatRoomData.Admin;
|
|
if (ChatRoomData && ChatRoomData.Ban)
|
|
Player.LastChatRoomBan = ChatRoomData.Ban;
|
|
|
|
ChatRoomLastName = ChatRoomData.Name;
|
|
ChatRoomLastBG = ChatRoomData.Background;
|
|
ChatRoomLastSize = ChatRoomData.Limit;
|
|
ChatRoomLastPrivate = ChatRoomData.Private;
|
|
ChatRoomLastDesc = ChatRoomData.Description;
|
|
ChatRoomLastAdmin = ChatRoomData.Admin;
|
|
ChatRoomLastBan = ChatRoomData.Ban;
|
|
}
|
|
} else {
|
|
Player.LastChatRoomBG = "";
|
|
Player.LastChatRoomPrivate = false;
|
|
ChatRoomLastName = "";
|
|
ChatRoomLastBG = "";
|
|
ChatRoomLastSize = 0;
|
|
ChatRoomLastPrivate = false;
|
|
ChatRoomLastDesc = "";
|
|
ChatRoomLastAdmin = [];
|
|
ChatRoomLastBan = [];
|
|
}
|
|
Player.LastChatRoom = room;
|
|
var P = {
|
|
LastChatRoom: Player.LastChatRoom,
|
|
LastChatRoomBG: Player.LastChatRoomBG,
|
|
LastChatRoomPrivate: Player.LastChatRoomPrivate,
|
|
LastChatRoomSize: Player.LastChatRoomSize,
|
|
LastChatRoomDesc: Player.LastChatRoomDesc,
|
|
LastChatRoomAdmin: Player.LastChatRoomAdmin.toString(),
|
|
LastChatRoomBan: Player.LastChatRoomBan.toString(),
|
|
|
|
};
|
|
ServerAccountUpdate.QueueData(P);
|
|
}
|
|
|
|
/**
|
|
* Triggers a chat room event for things like plugs and crotch ropes, will send a chat message if the chance is right.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomStimulationMessage(Context, Color = "#FFB0B0", FlashIntensity = 0, AlphaStrength = 140) {
|
|
if (CurrentScreen == "ChatRoom" && Player.ImmersionSettings && Player.ImmersionSettings.StimulationEvents) {
|
|
var C = Player;
|
|
if (Context == null || Context == "") Context = "StruggleAction";
|
|
|
|
var modBase = 0;
|
|
var modArousal = 0;
|
|
var modVibe = 0;
|
|
var modInflation = 0;
|
|
var modGag = 0;
|
|
|
|
if (ChatRoomArousalMsg_Chance[Context]) modBase = ChatRoomArousalMsg_Chance[Context];
|
|
if (ChatRoomArousalMsg_ChanceScaling[Context]) modArousal = ChatRoomArousalMsg_ChanceScaling[Context];
|
|
if (ChatRoomArousalMsg_ChanceVibeMod[Context]) modVibe = ChatRoomArousalMsg_ChanceVibeMod[Context];
|
|
if (ChatRoomArousalMsg_ChanceInflationMod[Context]) modInflation = ChatRoomArousalMsg_ChanceInflationMod[Context];
|
|
if (ChatRoomArousalMsg_ChanceGagMod[Context]) modGag = ChatRoomArousalMsg_ChanceGagMod[Context];
|
|
|
|
// Decide the trigger message
|
|
var trigPriority = 0.0;
|
|
var trigMsg = "";
|
|
var trigGroup = "";
|
|
var trigPlug = "";
|
|
var arousalAmount = 0; // Increases based on how many items
|
|
|
|
if (Context == "Flash") {
|
|
trigMsg = "Flash";
|
|
trigPriority = 2;
|
|
} else
|
|
for (let A = 0; A < C.Appearance.length; A++)
|
|
if ((C.Appearance[A].Asset != null) && (C.Appearance[A].Asset.Group.Family == C.AssetFamily)) {
|
|
var trigChance = 0;
|
|
var trigMsgTemp = "";
|
|
var Intensity = InventoryItemHasEffect(C.Appearance[A], "Vibrating", true) ? InventoryGetItemProperty(C.Appearance[A], "Intensity", true) : 0;
|
|
if (InventoryItemHasEffect(C.Appearance[A], "CrotchRope", true)) {
|
|
if (trigChance == 0) trigChance = modBase;
|
|
trigMsgTemp = "CrotchRope";
|
|
arousalAmount += 2;
|
|
} else if (Intensity > 0) {
|
|
if (trigChance == 0 && modVibe > 0) trigChance = modBase; // Some things are not affected by vibration, like kneeling
|
|
trigChance += modVibe * Intensity;
|
|
trigMsgTemp = "Vibe";
|
|
arousalAmount += Intensity;
|
|
if (InventoryItemHasEffect(C.Appearance[A], "FillVulva", true) && Math.random() < 0.5) {
|
|
trigMsgTemp = "VibePlugFront";
|
|
arousalAmount += 1;
|
|
if (trigPlug == "Back") trigPlug = "Both";
|
|
else trigPlug = "Front";
|
|
}
|
|
if (InventoryItemHasEffect(C.Appearance[A], "IsPlugged", true) && Math.random() < 0.5) {
|
|
if (trigMsgTemp == "Vibe")
|
|
trigMsgTemp = "VibePlugFront";
|
|
arousalAmount += 1;
|
|
if (trigPlug == "Front") trigPlug = "Both";
|
|
else trigPlug = "Back";
|
|
}
|
|
} else {
|
|
if (InventoryItemHasEffect(C.Appearance[A], "FillVulva", true)){
|
|
if (trigChance == 0) trigChance = modBase;
|
|
trigMsgTemp = "PlugFront";
|
|
arousalAmount += 1;
|
|
if (trigPlug == "Back") trigPlug = "Both";
|
|
else trigPlug = "Front";
|
|
}
|
|
if (InventoryItemHasEffect(C.Appearance[A], "IsPlugged", true)) {
|
|
if (trigChance == 0) trigChance = modBase;
|
|
if (trigMsgTemp == "")
|
|
trigMsgTemp = "PlugBack";
|
|
arousalAmount += 1;
|
|
if (trigPlug == "Front") trigPlug = "Both";
|
|
else trigPlug = "Back";
|
|
}
|
|
}
|
|
if (trigMsgTemp != "" && Player.ArousalSettings && Player.ArousalSettings.Progress > 0) {
|
|
trigChance += modArousal * Player.ArousalSettings.Progress/100;
|
|
}
|
|
if (trigMsgTemp != "") {
|
|
const Inflation = InventoryGetItemProperty(C.Appearance[A], "InflateLevel", true);
|
|
if (typeof Inflation === "number" && Inflation > 0) {
|
|
trigChance += modInflation * Inflation/4;
|
|
arousalAmount += Inflation/2;
|
|
}
|
|
}
|
|
|
|
if (trigPlug == "Both") {
|
|
if ((trigMsgTemp == "VibePlugFront" || trigMsgTemp == "VibePlugBack"
|
|
|| trigMsgTemp == "PlugFront" || trigMsgTemp == "PlugBack") && Math.random() > 0.7) {
|
|
trigMsgTemp = "PlugBoth";
|
|
arousalAmount += 1;
|
|
}
|
|
}
|
|
|
|
|
|
if (InventoryItemHasEffect(C.Appearance[A], "GagTotal", true) || InventoryItemHasEffect(C.Appearance[A], "GagTotal2", true)) {
|
|
if (trigChance == 0 && modGag > 0) trigChance = modBase; // Some things are not affected by vibration, like kneeling
|
|
trigChance += modGag;
|
|
|
|
|
|
if (trigChance > 0) {
|
|
arousalAmount += 12;
|
|
trigMsgTemp = "Gag";
|
|
}
|
|
}
|
|
|
|
if (trigMsgTemp != "" && Math.random() < trigChance && trigChance >= trigPriority) {
|
|
trigPriority = trigChance;
|
|
trigMsg = trigMsgTemp;
|
|
trigGroup = C.Appearance[A].Asset.Group.Name;
|
|
}
|
|
|
|
}
|
|
|
|
// Now we have a trigger message, hopefully!
|
|
if (trigMsg != "") {
|
|
|
|
if ((Player.ChatSettings != null) && (Player.ChatSettings.ShowActivities != null) && !Player.ChatSettings.ShowActivities) return;
|
|
|
|
if (Context == "Flash") {
|
|
ChatRoomPinkFlashTime = CommonTime() + (Math.random() + FlashIntensity) * 500;
|
|
ChatRoomPinkFlashColor = Color;
|
|
ChatRoomPinkFlashAlphaStrength = AlphaStrength;
|
|
} else {
|
|
// Increase player arousal to the zone
|
|
if (!Player.IsEdged() && Player.ArousalSettings && Player.ArousalSettings.Progress && Player.ArousalSettings.Progress < 70 - arousalAmount && trigMsgTemp != "Gag")
|
|
ActivityEffectFlat(Player, Player, arousalAmount, trigGroup, 1);
|
|
ChatRoomPinkFlashTime = CommonTime() + (Math.random() + arousalAmount/2.4) * 500;
|
|
ChatRoomPinkFlashColor = Color;
|
|
ChatRoomPinkFlashAlphaStrength = AlphaStrength;
|
|
CharacterSetFacialExpression(Player, "Blush", "VeryHigh", Math.ceil((ChatRoomPinkFlashTime - CommonTime())/250));
|
|
|
|
var index = Math.floor(Math.random() * 3);
|
|
ChatRoomMessage({ Content: "ChatRoomStimulationMessage"+trigMsg+""+index, Type: "Action", Sender: Player.MemberNumber });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when screen size or position changes or after screen load
|
|
* @param {boolean} load - If the reason for call was load (`true`) or window resize (`false`)
|
|
*/
|
|
function ChatRoomResize(load) {
|
|
if (
|
|
CharacterGetCurrent() == null
|
|
&& CurrentScreen == "ChatRoom"
|
|
&& document.getElementById("InputChat")
|
|
&& document.getElementById("TextAreaChatLog")
|
|
) {
|
|
ElementPositionFix("TextAreaChatLog", ChatRoomFontSize, 1005, 66, 988, 835);
|
|
ElementPosition("InputChat", 1456, 950, 900, 82);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws arousal screen filter
|
|
* @param {number} y1 - Y to draw filter at.
|
|
* @param {number} h - Height of filter
|
|
* @param {number} Width - Width of filter
|
|
* @param {number} ArousalOverride - Override to the existing arousal value
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomDrawArousalScreenFilter(y1, h, Width, ArousalOverride) {
|
|
let Progress = (ArousalOverride) ? ArousalOverride : Player.ArousalSettings.Progress;
|
|
let amplitude = 0.24 * Math.min(1, 2 - 1.5 * Progress/100); // Amplitude of the oscillation
|
|
let percent = Progress/100.0;
|
|
let level = Math.min(0.5, percent) + 0.5 * Math.pow(Math.max(0, percent*2 - 1), 4);
|
|
let oscillation = Math.sin(CommonTime() / 1000 % Math.PI);
|
|
let alpha = 0.6 * level * (0.99 - amplitude + amplitude * oscillation);
|
|
|
|
if (Player.ArousalSettings.VFXFilter == "VFXFilterHeavy") {
|
|
const Grad = MainCanvas.createLinearGradient(0, y1, 0, h);
|
|
let alphamin = Math.max(0, alpha / 2 - 0.05);
|
|
Grad.addColorStop(0, `rgba(255, 100, 176, ${alpha})`);
|
|
Grad.addColorStop(0.1 + 0.2*percent * (1.2 + 0.2 * oscillation), `rgba(255, 100, 176, ${alphamin})`);
|
|
Grad.addColorStop(0.5, `rgba(255, 100, 176, ${alphamin/2})`);
|
|
Grad.addColorStop(0.9 - 0.2*percent * (1.2 + 0.2 * oscillation), `rgba(255, 100, 176, ${alphamin})`);
|
|
Grad.addColorStop(1, `rgba(255, 100, 176, ${alpha})`);
|
|
MainCanvas.fillStyle = Grad;
|
|
MainCanvas.fillRect(0, y1, Width, h);
|
|
} else {
|
|
if (Player.ArousalSettings.VFXFilter != "VFXFilterMedium") {
|
|
alpha = (Progress >= 91) ? 0.25 : 0;
|
|
} else alpha /= 2;
|
|
if (alpha > 0)
|
|
DrawRect(0, y1, Width, h, `rgba(255, 176, 176, ${alpha})`);
|
|
}
|
|
}
|
|
/**
|
|
* Draws vibration screen filter for the specified player
|
|
* @param {number} y1 - Y to draw filter at.
|
|
* @param {number} h - Height of filter
|
|
* @param {number} Width - Width of filter
|
|
* @param {Character} C - Player to draw it for
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomVibrationScreenFilter(y1, h, Width, C) {
|
|
let VibratorLevelLower = 0;
|
|
let VibratorLevelUpper = 0;
|
|
for (let A = 0; A < C.Appearance.length; A++) {
|
|
if (C.Appearance[A] && C.Appearance[A].Property) {
|
|
let property = C.Appearance[A].Property;
|
|
if (property.Effect && property.Effect.includes("Vibrating") && property.Intensity >= 0) {
|
|
let intensity = property.Intensity + 1;
|
|
let group = (C.Appearance[A].Asset && C.Appearance[A].Asset.Group) ? C.Appearance[A].Asset.Group.Name : "";
|
|
if (group == "ItemVulva" || group == "ItemPelvis" || group == "ItemButt" || group == "ItemVulvaPiercings") {
|
|
VibratorLevelLower += (100 -VibratorLevelLower) * 0.7*Math.min(1, intensity/4);
|
|
} else {
|
|
VibratorLevelUpper += (100 -VibratorLevelUpper) * 0.7*Math.min(1, intensity/4);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ChatRoomDrawVibrationScreenFilter(y1, h, Width, VibratorLevelLower, VibratorLevelUpper);
|
|
}
|
|
|
|
/**
|
|
* Draws vibration screen filter
|
|
* @param {number} y1 - Y to draw filter at.
|
|
* @param {number} h - Height of filter
|
|
* @param {number} Width - Width of filter
|
|
* @param {number} VibratorLower - 1-100 Strength of the vibrator "Down There"
|
|
* @param {number} VibratorSides - 1-100 Strength of the vibrator at the breasts/nipples
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomDrawVibrationScreenFilter(y1, h, Width, VibratorLower, VibratorSides) {
|
|
let amplitude = 0.24; // Amplitude of the oscillation
|
|
let percentLower = VibratorLower/100.0;
|
|
let percentSides = VibratorSides/100.0;
|
|
let level = Math.min(0.5, Math.max(percentLower, percentSides)) + 0.5 * Math.pow(Math.max(0, Math.max(percentLower, percentSides)*2 - 1), 4);
|
|
let oscillation = Math.sin(CommonTime() / 1000 % Math.PI);
|
|
if (Player.ArousalSettings.VFXVibrator != "VFXVibratorAnimated") oscillation = 0;
|
|
let alpha = 0.6 * level * (0.99 - amplitude + amplitude * oscillation);
|
|
|
|
if (VibratorLower > 0) {
|
|
const Grad = MainCanvas.createRadialGradient(Width/2, y1, 0, Width/2, y1, h);
|
|
let alphamin = Math.max(0, alpha / 2 - 0.05);
|
|
let modifier = (Player.ArousalSettings.VFXVibrator == "VFXVibratorAnimated") ? Math.random() * 0.01: 0;
|
|
Grad.addColorStop(VibratorLower / 100 * (0.7 + modifier), `rgba(255, 100, 176, 0)`);
|
|
Grad.addColorStop(VibratorLower / 100 * (0.85 - 0.1*percentLower * (0.5 * oscillation)), `rgba(255, 100, 176, ${alphamin})`);
|
|
Grad.addColorStop(1, `rgba(255, 100, 176, ${alpha})`);
|
|
MainCanvas.fillStyle = Grad;
|
|
MainCanvas.fillRect(0, y1, Width, h);
|
|
}
|
|
if (VibratorSides > 0) {
|
|
let Grad = MainCanvas.createRadialGradient(0, 0, 0, 0, 0, Math.sqrt(h*h + Width*Width));
|
|
let alphamin = Math.max(0, alpha / 2 - 0.05);
|
|
let modifier = (Player.ArousalSettings.VFXVibrator == "VFXVibratorAnimated") ? Math.random() * 0.01: 0;
|
|
Grad.addColorStop(VibratorSides / 100 * (0.8 + modifier), `rgba(255, 100, 176, 0)`);
|
|
Grad.addColorStop(VibratorSides / 100 * (0.9 - 0.07*percentSides * (0.5 * oscillation)), `rgba(255, 100, 176, ${alphamin})`);
|
|
Grad.addColorStop(1, `rgba(255, 100, 176, ${alpha})`);
|
|
MainCanvas.fillStyle = Grad;
|
|
MainCanvas.fillRect(0, y1, Width, h);
|
|
|
|
Grad = MainCanvas.createRadialGradient(Width, 0, 0, Width, 0, Math.sqrt(h*h + Width*Width));
|
|
modifier = (Player.ArousalSettings.VFXVibrator == "VFXVibratorAnimated") ? Math.random() * 0.01: 0;
|
|
Grad.addColorStop(VibratorSides / 100 * (0.8 + modifier), `rgba(255, 100, 176, 0)`);
|
|
Grad.addColorStop(VibratorSides / 100 * (0.9 - 0.07*percentSides * (0.5 * oscillation)), `rgba(255, 100, 176, ${alphamin})`);
|
|
Grad.addColorStop(1, `rgba(255, 100, 176, ${alpha})`);
|
|
MainCanvas.fillStyle = Grad;
|
|
MainCanvas.fillRect(0, y1, Width, h);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runs the chatroom online bounty loop.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomUpdateOnlineBounty() {
|
|
if (KidnapLeagueSearchingPlayers.length > 0) {
|
|
let misc = InventoryGet(Player, "ItemMisc");
|
|
if (misc && misc.Asset && (misc.Asset.Name == "BountySuitcase" || misc.Asset.Name == "BountySuitcaseEmpty")) {
|
|
if (KidnapLeagueSearchFinishTime > 0 && CommonTime() > KidnapLeagueSearchFinishTime) {
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++) {
|
|
if (KidnapLeagueSearchingPlayers.includes(ChatRoomCharacter[C].MemberNumber)) {
|
|
ServerSend("ChatRoomChat", { Content: "ReceiveSuitcaseMoney", Type: "Hidden", Target: ChatRoomCharacter[C].MemberNumber});
|
|
}
|
|
}
|
|
if (misc.Asset.Name == "BountySuitcase") {
|
|
InventoryRemove(Player, "ItemMisc");
|
|
InventoryWear(Player, "BountySuitcaseEmpty", "ItemMisc");
|
|
ChatRoomMessage({ Content: "OnlineBountySuitcaseEnd", Type: "Action", Sender: Player.MemberNumber });
|
|
KidnapLeagueSearchFinishTime = 0;
|
|
KidnapLeagueSearchingPlayers = [];
|
|
ChatRoomCharacterItemUpdate(Player, "ItemMisc");
|
|
} else {
|
|
ChatRoomMessage({ Content: "OnlineBountySuitcaseEndOpened", Type: "Action", Sender: Player.MemberNumber });
|
|
KidnapLeagueSearchFinishTime = 0;
|
|
KidnapLeagueSearchingPlayers = [];
|
|
if (!misc.Property) misc.Property = {};
|
|
if (!misc.Property.Iterations) misc.Property.Iterations = 0;
|
|
misc.Property.Iterations = misc.Property.Iterations + 1;
|
|
ChatRoomCharacterItemUpdate(Player, "ItemMisc");
|
|
}
|
|
|
|
}
|
|
let KidnapLeagueSearchingPlayersNew = [];
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++) {
|
|
if (ChatRoomCharacter[C].CanInteract() && KidnapLeagueSearchingPlayers.includes(ChatRoomCharacter[C].MemberNumber)) {
|
|
KidnapLeagueSearchingPlayersNew.push(ChatRoomCharacter[C].MemberNumber);
|
|
}
|
|
}
|
|
KidnapLeagueSearchingPlayers = KidnapLeagueSearchingPlayersNew;
|
|
} else {
|
|
KidnapLeagueSearchingPlayers = [];
|
|
KidnapLeagueSearchFinishTime = 0;
|
|
}
|
|
} else {
|
|
if (KidnapLeagueSearchFinishTime > 0) {
|
|
if (InventoryIsWorn(Player,"BountySuitcase", "ItemMisc"))
|
|
ChatRoomPublishCustomAction("OnlineBountySuitcaseEndEarly", true, [
|
|
{ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber },
|
|
]);
|
|
else if (InventoryIsWorn(Player,"BountySuitcaseEmpty", "ItemMisc"))
|
|
ChatRoomPublishCustomAction("OnlineBountySuitcaseEndEarlyOpened", true, [
|
|
{ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber },
|
|
]);
|
|
}
|
|
|
|
KidnapLeagueSearchFinishTime = 0;
|
|
}
|
|
if (!Player.CanInteract()) {
|
|
KidnapLeagueOnlineBountyTarget = 0;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Runs the chatroom screen.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomRun() {
|
|
// Handles online bounty game
|
|
ChatRoomUpdateOnlineBounty();
|
|
|
|
// Draws the chat room controls
|
|
ChatRoomUpdateDisplay();
|
|
ChatRoomCreateElement();
|
|
ChatRoomFirstTimeHelp();
|
|
ChatRoomTarget();
|
|
ChatRoomBackground = "";
|
|
DrawRect(0, 0, 2000, 1000, "Black");
|
|
ChatRoomDrawCharacter(false);
|
|
if (ChatRoomChatHidden) {
|
|
ChatRoomChatHidden = false;
|
|
ChatRoomResize(false);
|
|
}
|
|
DrawButton(1905, 908, 90, 90, "", "White", "Icons/Chat.png");
|
|
if (!ChatRoomCanLeave() && ChatRoomSlowtimer != 0){//Player got interrupted while trying to leave. (Via a bind)
|
|
ServerSend("ChatRoomChat", { Content: "SlowLeaveInterrupt", Type: "Action", Dictionary: [{Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber}]});
|
|
ServerSend("ChatRoomChat", { Content: "SlowLeaveInterrupt", Type: "Hidden", Dictionary: [{Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber}]});
|
|
ChatRoomSlowtimer = 0;
|
|
ChatRoomSlowStop = false;
|
|
}
|
|
|
|
const PlayerIsSlow = Player.IsSlow();
|
|
|
|
// If the player is slow (ex: ball & chains), she can leave the room with a timer and can be blocked by others
|
|
if (PlayerIsSlow && ChatRoomCanLeave() && (ChatRoomSlowStop == false)) {
|
|
if (ChatRoomSlowtimer == 0) DrawButton(1005, 2, 120, 60, "", "#FFFF00", "Icons/Rectangle/Exit.png", TextGet("MenuLeave"));
|
|
if ((CurrentTime < ChatRoomSlowtimer) && (ChatRoomSlowtimer != 0)) DrawButton(1005, 2, 120, 60, "", "White", "Icons/Rectangle/Cancel.png", TextGet("MenuCancel"));
|
|
if ((CurrentTime > ChatRoomSlowtimer) && (ChatRoomSlowtimer != 0)) {
|
|
ChatRoomSlowtimer = 0;
|
|
ChatRoomSlowStop = false;
|
|
DialogLentLockpicks = false;
|
|
ChatRoomClearAllElements();
|
|
ChatRoomSetLastChatRoom("");
|
|
ServerSend("ChatRoomLeave", "");
|
|
CommonSetScreen("Online", "ChatSearch");
|
|
}
|
|
}
|
|
|
|
if (CurrentTime > ChatRoomGetUpTimer) {
|
|
ChatRoomGetUpTimer = 0;
|
|
}
|
|
|
|
// If the player is slow and was stopped from leaving by another player
|
|
if ((ChatRoomSlowStop == true) && PlayerIsSlow) {
|
|
DrawButton(1005, 2, 120, 60, "", "Pink", "Icons/Rectangle/Exit.png", TextGet("MenuLeave"));
|
|
if (CurrentTime > ChatRoomSlowtimer) {
|
|
ChatRoomSlowtimer = 0;
|
|
ChatRoomSlowStop = false;
|
|
}
|
|
}
|
|
|
|
// Draws the top buttons in pink if they aren't available
|
|
if (!PlayerIsSlow || (ChatRoomSlowtimer == 0 && !ChatRoomCanLeave())){
|
|
if (ChatRoomSlowtimer != 0) ChatRoomSlowtimer = 0;
|
|
DrawButton(1005, 2, 120, 60, "", (ChatRoomCanLeave()) ? "White" : "Pink", "Icons/Rectangle/Exit.png", TextGet("MenuLeave"));
|
|
}
|
|
|
|
// Draw the buttons at the top-right
|
|
ChatRoomMenuDraw();
|
|
|
|
// In orgasm mode, we add a pink filter and different controls depending on the stage. The pink filter shows a little above 90
|
|
if ((Player.ArousalSettings != null) && (Player.ArousalSettings.Active != null) && (Player.ArousalSettings.Active != "Inactive") && (Player.ArousalSettings.Active != "NoMeter")) {
|
|
if ((Player.ArousalSettings.OrgasmTimer != null) && (typeof Player.ArousalSettings.OrgasmTimer === "number") && !isNaN(Player.ArousalSettings.OrgasmTimer) && (Player.ArousalSettings.OrgasmTimer > 0)) {
|
|
DrawRect(0, 0, 1003, 1000, "#FFB0B0B0");
|
|
DrawRect(1003, 0, 993, 63, "#FFB0B0B0");
|
|
if (Player.ArousalSettings.OrgasmStage == null) Player.ArousalSettings.OrgasmStage = 0;
|
|
if (Player.ArousalSettings.OrgasmStage == 0) {
|
|
DrawText(TextGet("OrgasmComing"), 500, 410, "White", "Black");
|
|
DrawButton(200, 532, 250, 64, TextGet("OrgasmTryResist"), "White");
|
|
DrawButton(550, 532, 250, 64, TextGet("OrgasmSurrender"), "White");
|
|
}
|
|
if (Player.ArousalSettings.OrgasmStage == 1) DrawButton(ActivityOrgasmGameButtonX, ActivityOrgasmGameButtonY, 250, 64, ActivityOrgasmResistLabel, "White");
|
|
if (ActivityOrgasmRuined) ActivityOrgasmControl();
|
|
if (Player.ArousalSettings.OrgasmStage == 2) DrawText(TextGet("OrgasmRecovering"), 500, 500, "White", "Black");
|
|
ActivityOrgasmProgressBar(50, 970);
|
|
} else if ((Player.ArousalSettings.Progress != null) && (Player.ArousalSettings.Progress >= 1) && (Player.ArousalSettings.Progress <= 99) && !CommonPhotoMode) {
|
|
let y1 = 0;
|
|
let h = 1000;
|
|
|
|
if (ChatRoomCharacterCount == 3) {y1 = 50; h = 900;}
|
|
else if (ChatRoomCharacterCount == 4) {y1 = 150; h = 700;}
|
|
else if (ChatRoomCharacterCount == 5) {y1 = 250; h = 500;}
|
|
|
|
ChatRoomDrawArousalScreenFilter(y1, h, 1003, Player.ArousalSettings.Progress);
|
|
}
|
|
}
|
|
|
|
if (Player.ArousalSettings.VFXVibrator == "VFXVibratorSolid" || Player.ArousalSettings.VFXVibrator == "VFXVibratorAnimated") {
|
|
let y1 = 0;
|
|
let h = 1000;
|
|
|
|
if (ChatRoomCharacterCount == 3) {y1 = 50; h = 900;}
|
|
else if (ChatRoomCharacterCount == 4) {y1 = 150; h = 700;}
|
|
else if (ChatRoomCharacterCount == 5) {y1 = 250; h = 500;}
|
|
|
|
ChatRoomVibrationScreenFilter(y1, h, 1003, Player);
|
|
}
|
|
|
|
if ((Player.ImmersionSettings != null && Player.GraphicsSettings != null) && (Player.ImmersionSettings.StimulationEvents && Player.GraphicsSettings.StimulationFlash) && ChatRoomPinkFlashTime > CommonTime()) {
|
|
let FlashTime = ChatRoomPinkFlashTime - CommonTime(); // ChatRoomPinkFlashTime is the end of the flash. The flash is brighter based on the distance to the end.
|
|
let PinkFlashAlpha = DrawGetScreenFlash(FlashTime);
|
|
if (
|
|
(ChatRoomCharacterCount <= 2) || (ChatRoomCharacterCount >= 6) ||
|
|
(Player.GameplaySettings && (Player.GameplaySettings.SensDepChatLog == "SensDepExtreme") && (Player.GetBlindLevel() >= 3))
|
|
)
|
|
DrawRect(0, 0, 2000, 1000, ChatRoomPinkFlashColor + PinkFlashAlpha);
|
|
else if (ChatRoomCharacterCount == 3) DrawRect(0, 50, 1003, 900, ChatRoomPinkFlashColor + PinkFlashAlpha);
|
|
else if (ChatRoomCharacterCount == 4) DrawRect(0, 150, 1003, 700, ChatRoomPinkFlashColor + PinkFlashAlpha);
|
|
else if (ChatRoomCharacterCount == 5) DrawRect(0, 250, 1003, 500, ChatRoomPinkFlashColor + PinkFlashAlpha);
|
|
}
|
|
|
|
|
|
// Runs any needed online game script
|
|
OnlineGameRun();
|
|
|
|
// Clear notifications if needed
|
|
ChatRoomNotificationReset();
|
|
|
|
// Recreates the chatroom with the stored chatroom data if necessary
|
|
ChatRoomRecreate();
|
|
}
|
|
|
|
/**
|
|
* Draws the chat room menu buttons
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomMenuDraw() {
|
|
const Space = 870 / (ChatRoomMenuButtons.length - 1);
|
|
let ButtonColor = "White";
|
|
for (let B = 0; B < ChatRoomMenuButtons.length; B++) {
|
|
let Button = ChatRoomMenuButtons[B];
|
|
if (Button === "Exit") continue; // handled in ChatRoomRun()
|
|
const ImageSuffix = Button === "Icons" ? ChatRoomHideIconState.toString() : "";
|
|
if (Button === "Kneel" && !Player.CanKneel()) {
|
|
if (ChatRoomGetUpTimer === 0 && (ChatRoomCanAttemptStand() || ChatRoomCanAttemptKneel())) {
|
|
ButtonColor = "Yellow";
|
|
} else {
|
|
ButtonColor = "Pink";
|
|
}
|
|
} else if (Button === "Dress" && (!Player.CanChange() || !OnlineGameAllowChange())) {
|
|
ButtonColor = "Pink";
|
|
} else {
|
|
ButtonColor = "White";
|
|
}
|
|
DrawButton(1005 + Space * B, 2, 120, 60, "", ButtonColor, "Icons/Rectangle/" + Button + ImageSuffix + ".png", TextGet("Menu" + Button));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles clicks the chatroom screen.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomClick() {
|
|
|
|
// In orgasm mode, we do not allow any clicks expect the chat
|
|
if (MouseIn(1905, 910, 90, 90)) ChatRoomSendChat();
|
|
if ((Player.ArousalSettings != null) && (Player.ArousalSettings.OrgasmTimer != null) && (typeof Player.ArousalSettings.OrgasmTimer === "number") && !isNaN(Player.ArousalSettings.OrgasmTimer) && (Player.ArousalSettings.OrgasmTimer > 0)) {
|
|
|
|
// On stage 0, the player can choose to resist the orgasm or not. At 1, the player plays a mini-game to fight her orgasm
|
|
if (MouseIn(200, 532, 250, 68) && (Player.ArousalSettings.OrgasmStage == 0)) ActivityOrgasmGameGenerate(0);
|
|
if (MouseIn(550, 532, 250, 68) && (Player.ArousalSettings.OrgasmStage == 0)) ActivityOrgasmStart(Player);
|
|
if ((MouseX >= ActivityOrgasmGameButtonX) && (MouseX <= ActivityOrgasmGameButtonX + 250) && (MouseY >= ActivityOrgasmGameButtonY) && (MouseY <= ActivityOrgasmGameButtonY + 64) && (Player.ArousalSettings.OrgasmStage == 1)) ActivityOrgasmGameGenerate(ActivityOrgasmGameProgress + 1);
|
|
return;
|
|
|
|
}
|
|
|
|
// When the user chats or clicks on a character
|
|
if (MouseIn(0, 0, 1000, 1000)) ChatRoomDrawCharacter(true);
|
|
|
|
// When the user clicks a menu button in the top-right
|
|
if (MouseYIn(0, 62)) ChatRoomMenuClick();
|
|
}
|
|
|
|
/**
|
|
* Process chat room menu button clicks
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomMenuClick() {
|
|
const Space = 870 / (ChatRoomMenuButtons.length - 1);
|
|
for (let B = 0; B < ChatRoomMenuButtons.length; B++) {
|
|
if (MouseXIn(1005 + Space * B, 120)) {
|
|
switch (ChatRoomMenuButtons[B]) {
|
|
case "Exit": {
|
|
const PlayerIsSlow = Player.IsSlow();
|
|
// When the user leaves
|
|
if (ChatRoomCanLeave() && !PlayerIsSlow) {
|
|
DialogLentLockpicks = false;
|
|
ChatRoomClearAllElements();
|
|
ServerSend("ChatRoomLeave", "");
|
|
ChatRoomSetLastChatRoom("");
|
|
// Clear leash since the player has escaped
|
|
ChatRoomLeashPlayer = null;
|
|
CommonSetScreen("Online", "ChatSearch");
|
|
CharacterDeleteAllOnline();
|
|
}
|
|
// When the player is slow and attempts to leave
|
|
if (ChatRoomCanLeave() && PlayerIsSlow) {
|
|
// If the player clicked to leave, we start a timer based on evasion level and send a chat message
|
|
if ((ChatRoomSlowtimer == 0) && (ChatRoomSlowStop == false)) {
|
|
ServerSend("ChatRoomChat", { Content: "SlowLeaveAttempt", Type: "Action", Dictionary: [{ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber }] });
|
|
ChatRoomSlowtimer = CurrentTime + (10 * (1000 - (50 * SkillGetLevelReal(Player, "Evasion"))));
|
|
}
|
|
// If the player clicked to cancel leaving, we alert the room and stop the timer
|
|
else if ((ChatRoomSlowtimer != 0) && (ChatRoomSlowStop == false)) {
|
|
ServerSend("ChatRoomChat", { Content: "SlowLeaveCancel", Type: "Action", Dictionary: [{ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber }] });
|
|
ChatRoomSlowtimer = 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case "Cut":
|
|
// When the user wants to remove the top part of his chat to speed up the screen, we only keep the last 20 entries
|
|
var L = document.getElementById("TextAreaChatLog");
|
|
while (L.childElementCount > 20)
|
|
L.removeChild(L.childNodes[0]);
|
|
ElementScrollToEnd("TextAreaChatLog");
|
|
break;
|
|
case "GameOption":
|
|
// The cut button can become the game option button if there's an online game going on
|
|
document.getElementById("InputChat").style.display = "none";
|
|
document.getElementById("TextAreaChatLog").style.display = "none";
|
|
CommonSetScreen("Online", "Game" + ChatRoomGame);
|
|
break;
|
|
case "Kneel":
|
|
// When the user character kneels
|
|
if (Player.CanKneel()) {
|
|
const PlayerIsKneeling = Player.ActivePose && Player.ActivePose.includes("Kneel");
|
|
ServerSend("ChatRoomChat", { Content: PlayerIsKneeling ? "StandUp" : "KneelDown", Type: "Action", Dictionary: [{ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber }] });
|
|
FuturisticTrainingBeltStandUpFlag = Player.IsKneeling();
|
|
CharacterSetActivePose(Player, PlayerIsKneeling ? "BaseLower" : "Kneel");
|
|
ChatRoomStimulationMessage("Kneel");
|
|
ServerSend("ChatRoomCharacterPoseUpdate", { Pose: Player.ActivePose });
|
|
} else if (ChatRoomGetUpTimer == 0 && (ChatRoomCanAttemptStand() || ChatRoomCanAttemptKneel())) { // If the player can theoretically get up, we start a minigame!
|
|
var diff = 0;
|
|
if (Player.IsBlind()) diff += 1;
|
|
if (Player.IsKneeling()) diff += 2;
|
|
if (Player.IsDeaf()) diff += 1;
|
|
if (InventoryGet(Player, "ItemTorso") || InventoryGroupIsBlocked(Player, "ItemTorso")) diff += 1;
|
|
if (InventoryGroupIsBlocked(Player, "ItemHands")) diff += 1;
|
|
if (InventoryGet(Player, "ItemArms")) diff += 1;
|
|
if (InventoryGet(Player, "ItemLegs") || InventoryGroupIsBlocked(Player, "ItemLegs")) diff += 1;
|
|
if (InventoryGet(Player, "ItemFeet") || InventoryGroupIsBlocked(Player, "ItemFeet")) diff += 1;
|
|
if (InventoryGet(Player, "ItemBoots")) diff += 2;
|
|
|
|
MiniGameStart("GetUp", diff, "ChatRoomAttemptStandMinigameEnd");
|
|
}
|
|
break;
|
|
case "Icons":
|
|
// When the user toggles icon visibility
|
|
ChatRoomHideIconState += 1;
|
|
if (ChatRoomHideIconState > 3) ChatRoomHideIconState = 0;
|
|
break;
|
|
case "Camera":
|
|
// When the user takes a photo of the room
|
|
ChatRoomPhotoFullRoom();
|
|
break;
|
|
case "Dress":
|
|
// When the user wants to change clothes
|
|
if (Player.CanChange() && OnlineGameAllowChange()) {
|
|
document.getElementById("InputChat").style.display = "none";
|
|
document.getElementById("TextAreaChatLog").style.display = "none";
|
|
CharacterAppearanceReturnRoom = "ChatRoom";
|
|
CharacterAppearanceReturnModule = "Online";
|
|
CharacterAppearanceLoadCharacter(Player);
|
|
}
|
|
break;
|
|
case "Profile":
|
|
// When the user checks her profile
|
|
document.getElementById("InputChat").style.display = "none";
|
|
document.getElementById("TextAreaChatLog").style.display = "none";
|
|
InformationSheetLoadCharacter(Player);
|
|
break;
|
|
case "Admin":
|
|
// When the user enters the room administration screen
|
|
document.getElementById("InputChat").style.display = "none";
|
|
document.getElementById("TextAreaChatLog").style.display = "none";
|
|
CommonSetScreen("Online", "ChatAdmin");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function ChatRoomAttemptStandMinigameEnd() {
|
|
|
|
if (MiniGameVictory) {
|
|
if (MiniGameType == "GetUp"){
|
|
ServerSend("ChatRoomChat", { Content: (!Player.IsKneeling()) ? "KneelDownPass" : "StandUpPass", Type: "Action", Dictionary: [{ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber }] });
|
|
FuturisticTrainingBeltStandUpFlag = Player.IsKneeling();
|
|
CharacterSetActivePose(Player, (!Player.IsKneeling()) ? "Kneel" : null, true);
|
|
ServerSend("ChatRoomCharacterPoseUpdate", { Pose: Player.ActivePose });
|
|
}
|
|
} else {
|
|
if (MiniGameType == "GetUp") {
|
|
ChatRoomGetUpTimer = CurrentTime + 15000;
|
|
ServerSend("ChatRoomChat", { Content: (!Player.IsKneeling()) ? "KneelDownFail" : "StandUpFail", Type: "Action", Dictionary: [{ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber }] });
|
|
if (!Player.IsKneeling()) {
|
|
CharacterSetFacialExpression(Player, "Eyebrows", "Soft", 15);
|
|
CharacterSetFacialExpression(Player, "Blush", "Medium", 15);
|
|
CharacterSetFacialExpression(Player, "Eyes", "Dizzy", 15);
|
|
}
|
|
}
|
|
}
|
|
|
|
CommonSetScreen("Online", "ChatRoom");
|
|
}
|
|
|
|
/**
|
|
* Checks if the player can leave the chatroom.
|
|
* @returns {boolean} - Returns TRUE if the player can leave the current chat room.
|
|
*/
|
|
function ChatRoomCanLeave() {
|
|
if (ChatRoomLeashPlayer != null) {
|
|
if (ChatRoomCanBeLeashedBy(0, Player)) {
|
|
return false;
|
|
} else ChatRoomLeashPlayer = null;
|
|
}
|
|
if (!Player.CanWalk()) return false; // Cannot leave if cannot walk
|
|
if (!ChatRoomData.Locked || ChatRoomPlayerIsAdmin()) return true; // Can leave if the room isn't locked or is an administrator
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++)
|
|
if (ChatRoomData.Admin.indexOf(ChatRoomCharacter[C].MemberNumber) >= 0)
|
|
return false; // Cannot leave if the room is locked and there's an administrator inside
|
|
return true; // Can leave if the room is locked and there's no administrator inside
|
|
}
|
|
|
|
/**
|
|
* Handles keyboard shortcuts in the chatroom screen.
|
|
* @param {KeyboardEvent} event - The event that triggered this
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomKeyDown(event) {
|
|
|
|
// If the input text is not focused and not on mobile, set the focus to it
|
|
if (document.activeElement.id != "InputChat") ElementFocus("InputChat");
|
|
|
|
if (KeyPress == 9 && !event.shiftKey) {
|
|
event.preventDefault();
|
|
CommandAutoComplete(ElementValue("InputChat"));
|
|
}
|
|
|
|
// The ENTER key sends the chat. The "preventDefault" is needed for <textarea>, otherwise it adds a new line after clearing the field
|
|
if (KeyPress == 13 && !event.shiftKey) {
|
|
event.preventDefault();
|
|
ChatRoomSendChat();
|
|
}
|
|
|
|
// On page up, we show the previous chat typed
|
|
if (KeyPress == 33) {
|
|
if (ChatRoomLastMessageIndex > 0) ChatRoomLastMessageIndex--;
|
|
ElementValue("InputChat", ChatRoomLastMessage[ChatRoomLastMessageIndex]);
|
|
}
|
|
|
|
// On page down, we show the next chat typed
|
|
if (KeyPress == 34) {
|
|
ChatRoomLastMessageIndex++;
|
|
if (ChatRoomLastMessageIndex > ChatRoomLastMessage.length - 1) ChatRoomLastMessageIndex = ChatRoomLastMessage.length - 1;
|
|
ElementValue("InputChat", ChatRoomLastMessage[ChatRoomLastMessageIndex]);
|
|
}
|
|
|
|
// On escape, scroll to the bottom of the chat
|
|
if (KeyPress == 27) ElementScrollToEnd("TextAreaChatLog");
|
|
}
|
|
|
|
/**
|
|
* Sends the chat message to the room
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSendChat() {
|
|
|
|
// If there's a message to send
|
|
const msg = ElementValue("InputChat").trim();
|
|
if (msg != "") {
|
|
|
|
// Keeps the chat log in memory so it can be accessed with pageup/pagedown
|
|
ChatRoomLastMessage.push(msg);
|
|
ChatRoomLastMessageIndex = ChatRoomLastMessage.length;
|
|
|
|
CommandParse(msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends message to user with HTML tags
|
|
* @param {string} Content - InnerHTML for the message
|
|
* @param {number} [Timeout] - total time to display the message in ms
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomSendLocal(Content, Timeout) {
|
|
ChatRoomMessage({
|
|
Sender: Player.MemberNumber,
|
|
Type: "LocalMessage",
|
|
Content, Timeout,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Removes (*) (/me) (/action) then sends message as emote
|
|
* @param {string} msg - Emote message
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomSendEmote(msg) {
|
|
if (Player.ChatSettings.MuStylePoses && msg.startsWith(":")) msg = msg.substring(1);
|
|
else {
|
|
msg = msg.replace(/^\*/, "").replace(/\*$/, "");
|
|
if (msg.startsWith(CommandsKey + "me ")) msg = msg.replace(CommandsKey + "me ", "");
|
|
if (msg.startsWith(CommandsKey + "action ")) msg = msg.replace(CommandsKey + "action ", "*");
|
|
}
|
|
msg = msg.trim();
|
|
if (msg != "" && msg != "*") ServerSend("ChatRoomChat", { Content: msg, Type: "Emote" });
|
|
}
|
|
|
|
/**
|
|
* Publishes common player actions (add, remove, swap) to the chat.
|
|
* @param {Character} C - Character on which the action is done.
|
|
* @param {Item} StruggleProgressPrevItem - The item that has been removed.
|
|
* @param {Item} StruggleProgressNextItem - The item that has been added.
|
|
* @param {boolean} LeaveDialog - Whether to leave the current dialog after publishing the action.
|
|
* @param {string} [Action] - Action modifier
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomPublishAction(C, StruggleProgressPrevItem, StruggleProgressNextItem, LeaveDialog, Action = null) {
|
|
if (CurrentScreen == "ChatRoom") {
|
|
|
|
// Prepares the message
|
|
var msg = "";
|
|
var Dictionary = [];
|
|
if (Action == null) {
|
|
if ((StruggleProgressPrevItem != null) && (StruggleProgressNextItem != null) && (StruggleProgressPrevItem.Asset.Name == StruggleProgressNextItem.Asset.Name) && (StruggleProgressPrevItem.Color != StruggleProgressNextItem.Color)) msg = "ActionChangeColor";
|
|
else if ((StruggleProgressPrevItem != null) && (StruggleProgressNextItem != null) && !StruggleProgressNextItem.Asset.IsLock) msg = "ActionSwap";
|
|
else if ((StruggleProgressPrevItem != null) && (StruggleProgressNextItem != null) && StruggleProgressNextItem.Asset.IsLock) msg = "ActionAddLock";
|
|
else if (InventoryItemHasEffect(StruggleProgressNextItem, "Lock", false)) msg = "ActionLock";
|
|
else if ((StruggleProgressNextItem != null) && (!StruggleProgressNextItem.Asset.Wear) && (StruggleProgressNextItem.Asset.DynamicActivity(Player) != null)) msg = "ActionActivity" + StruggleProgressNextItem.Asset.DynamicActivity(Player);
|
|
else if (StruggleProgressNextItem != null) msg = "ActionUse";
|
|
else if (InventoryItemHasEffect(StruggleProgressPrevItem, "Lock")) msg = "ActionUnlockAndRemove";
|
|
else msg = "ActionRemove";
|
|
} else if (Action == "interrupted") {
|
|
if ((StruggleProgressPrevItem != null) && (StruggleProgressNextItem != null) && !StruggleProgressNextItem.Asset.IsLock) msg = "ActionInterruptedSwap";
|
|
else if (StruggleProgressNextItem != null) msg = "ActionInterruptedAdd";
|
|
else msg = "ActionInterruptedRemove";
|
|
Dictionary.push({ Tag: "TargetCharacter", Text: C.Name, MemberNumber: C.MemberNumber });
|
|
} else msg = Action;
|
|
|
|
// Replaces the action tags to build the phrase
|
|
Dictionary.push({ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber });
|
|
Dictionary.push({ Tag: "DestinationCharacter", Text: C.Name, MemberNumber: C.MemberNumber });
|
|
if (StruggleProgressPrevItem != null) Dictionary.push({ Tag: "PrevAsset", AssetName: StruggleProgressPrevItem.Asset.Name });
|
|
if (StruggleProgressNextItem != null) Dictionary.push({ Tag: "NextAsset", AssetName: StruggleProgressNextItem.Asset.Name });
|
|
if (C.FocusGroup != null) Dictionary.push({ Tag: "FocusAssetGroup", AssetGroupName: C.FocusGroup.Name });
|
|
|
|
// Prepares the item packet to be sent to other players in the chatroom
|
|
ChatRoomCharacterItemUpdate(C);
|
|
|
|
// Sends the result to the server and leaves the dialog if we need to
|
|
ServerSend("ChatRoomChat", { Content: msg, Type: "Action", Dictionary: Dictionary });
|
|
if (LeaveDialog && (CurrentCharacter != null)) DialogLeave();
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates an item on character for everyone in a chat room - replaces ChatRoomCharacterUpdate to cut on the lag.
|
|
* @param {Character} C - Character to update.
|
|
* @param {string} [Group] - Item group to update.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomCharacterItemUpdate(C, Group) {
|
|
if ((Group == null) && (C.FocusGroup != null)) Group = C.FocusGroup.Name;
|
|
if ((CurrentScreen == "ChatRoom") && (Group != null)) {
|
|
if (ChatRoomData && ChatRoomData.Character) {
|
|
// Single item updates aren't sent back to the source member, so update the ChatRoomData accordingly
|
|
if (ChatRoomData && ChatRoomData.Character) {
|
|
const characterIndex = ChatRoomData.Character.findIndex((char) => char.MemberNumber === C.MemberNumber);
|
|
if (characterIndex !== -1) {
|
|
ChatRoomData.Character[characterIndex] = C;
|
|
}
|
|
}
|
|
}
|
|
|
|
const Item = InventoryGet(C, Group);
|
|
const P = {};
|
|
P.Target = C.MemberNumber;
|
|
P.Group = Group;
|
|
P.Name = (Item != null) ? Item.Asset.Name : undefined;
|
|
P.Color = ((Item != null) && (Item.Color != null)) ? Item.Color : "Default";
|
|
P.Difficulty = (Item != null) ? Item.Difficulty - Item.Asset.Difficulty : SkillGetWithRatio("Bondage");
|
|
P.Property = ((Item != null) && (Item.Property != null)) ? Item.Property : undefined;
|
|
ServerSend("ChatRoomCharacterItemUpdate", P);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Publishes a custom action to the chat
|
|
* @param {string} msg - Tag of the action to send
|
|
* @param {boolean} LeaveDialog - Whether or not the dialog should be left.
|
|
* @param {ChatMessageDictionary} Dictionary - Dictionary of tags and data to send
|
|
* to the room.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomPublishCustomAction(msg, LeaveDialog, Dictionary) {
|
|
if (CurrentScreen == "ChatRoom") {
|
|
ServerSend("ChatRoomChat", { Content: msg, Type: "Action", Dictionary: Dictionary });
|
|
var C = CharacterGetCurrent();
|
|
if (C) ChatRoomCharacterItemUpdate(C);
|
|
if (LeaveDialog && (C != null)) DialogLeave();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pushes the new character data/appearance to the server.
|
|
* @param {Character} C - Character to update.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomCharacterUpdate(C) {
|
|
if (ChatRoomAllowCharacterUpdate) {
|
|
var data = {
|
|
ID: (C.ID == 0) ? Player.OnlineID : C.AccountName.replace("Online-", ""),
|
|
ActivePose: C.ActivePose,
|
|
Appearance: ServerAppearanceBundle(C.Appearance)
|
|
};
|
|
ServerSend("ChatRoomCharacterUpdate", data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Escapes a given string.
|
|
* @param {string} str - Text to escape.
|
|
* @returns {string} - Escaped string.
|
|
*/
|
|
function ChatRoomHTMLEntities(str) {
|
|
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
}
|
|
|
|
/**
|
|
* Handles the reception of a chatroom message. Ghost players' messages are ignored.
|
|
* @param {object} data - Message object containing things like the message type, sender, content, etc.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomMessage(data) {
|
|
|
|
// Make sure the message is valid (needs a Sender and Content)
|
|
if ((data != null) && (typeof data === "object") && (data.Content != null) && (typeof data.Content === "string") && (data.Content != "") && (data.Sender != null) && (typeof data.Sender === "number")) {
|
|
|
|
if (data.Content == "ServerUpdateRoom") {
|
|
// If we must reset the current game played in the room
|
|
OnlineGameReset();
|
|
|
|
// If we must garble the chatroom name (immersion settings.)
|
|
if (Array.isArray(data.Dictionary)) {
|
|
let ChatRoomNameDictTag = data.Dictionary.find(el => el.Tag === "ChatRoomName");
|
|
if (ChatRoomNameDictTag) {
|
|
ChatRoomNameDictTag.Text = ChatSearchMuffle(ChatRoomNameDictTag.Text);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exits right away if the sender is ghosted
|
|
if (Player.GhostList.indexOf(data.Sender) >= 0) return;
|
|
|
|
// Make sure the sender is in the room
|
|
var SenderCharacter = null;
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++)
|
|
if (ChatRoomCharacter[C].MemberNumber == data.Sender) {
|
|
SenderCharacter = ChatRoomCharacter[C];
|
|
break;
|
|
}
|
|
|
|
// If we found the sender
|
|
if (SenderCharacter != null) {
|
|
// Replace < and > characters to prevent HTML injections
|
|
var msg = ChatRoomHTMLEntities(data.Content);
|
|
|
|
// Hidden messages are processed separately, they are used by chat room mini-games / events
|
|
if (data.Type == "Hidden") {
|
|
if (msg == "RuleInfoGet") ChatRoomGetLoadRules(SenderCharacter);
|
|
else if (msg == "RuleInfoSet") ChatRoomSetLoadRules(SenderCharacter, data.Dictionary);
|
|
else if (msg.startsWith("StruggleAssist")) {
|
|
let A = parseInt( msg.substr("StruggleAssist".length));
|
|
if ((A >= 1) && (A <= 7)) {
|
|
ChatRoomStruggleAssistTimer = CurrentTime + 60000;
|
|
ChatRoomStruggleAssistBonus = A;
|
|
}
|
|
}
|
|
else if (msg == "SlowStop"){
|
|
ChatRoomSlowtimer = CurrentTime + 45000;
|
|
ChatRoomSlowStop = true;
|
|
}
|
|
else if (msg.startsWith("MaidDrinkPick")){
|
|
let A = parseInt(msg.substr("MaidDrinkPick".length));
|
|
if ((A == 0) || (A == 5) || (A == 10)) MaidQuartersOnlineDrinkPick(data.Sender, A);
|
|
}
|
|
else if (msg.startsWith("PayQuest")) ChatRoomPayQuest(data);
|
|
else if (msg.startsWith("OwnerRule")) data = ChatRoomSetRule(data);
|
|
else if (msg.startsWith("LoverRule")) data = ChatRoomSetRule(data);
|
|
else if (msg == "HoldLeash"){
|
|
if (SenderCharacter.MemberNumber != ChatRoomLeashPlayer && ChatRoomLeashPlayer != null) {
|
|
ServerSend("ChatRoomChat", { Content: "RemoveLeash", Type: "Hidden", Target: ChatRoomLeashPlayer });
|
|
}
|
|
if (ChatRoomCanBeLeashedBy(SenderCharacter.MemberNumber, Player)) {
|
|
ChatRoomLeashPlayer = SenderCharacter.MemberNumber;
|
|
} else {
|
|
ServerSend("ChatRoomChat", { Content: "RemoveLeash", Type: "Hidden", Target: SenderCharacter.MemberNumber });
|
|
}
|
|
}
|
|
else if (msg == "StopHoldLeash"){
|
|
if (SenderCharacter.MemberNumber == ChatRoomLeashPlayer) {
|
|
ChatRoomLeashPlayer = null;
|
|
}
|
|
}
|
|
else if (msg == "PingHoldLeash"){ // The dom will ping all players on her leash list and ones that no longer have her as their leasher will remove it
|
|
if (SenderCharacter.MemberNumber != ChatRoomLeashPlayer || !ChatRoomCanBeLeashedBy(SenderCharacter.MemberNumber, Player)) {
|
|
ServerSend("ChatRoomChat", { Content: "RemoveLeash", Type: "Hidden", Target: SenderCharacter.MemberNumber });
|
|
}
|
|
}
|
|
else if (msg == "RemoveLeash" || msg == "RemoveLeashNotFriend") {
|
|
if (ChatRoomLeashList.indexOf(SenderCharacter.MemberNumber) >= 0) {
|
|
ChatRoomLeashList.splice(ChatRoomLeashList.indexOf(SenderCharacter.MemberNumber), 1);
|
|
}
|
|
}
|
|
else if (msg == "GiveLockpicks") DialogLentLockpicks = true;
|
|
else if (msg == "RequestFullKinkyDungeonData") {
|
|
KinkyDungeonStreamingPlayers.push(SenderCharacter.MemberNumber);
|
|
if (CurrentScreen == "KinkyDungeon")
|
|
KinkyDungeonSendData(KinkyDungeonPackData(true, true, true, true), SenderCharacter.MemberNumber);
|
|
}
|
|
else if (msg == "TakeSuitcase"){
|
|
if (!Player.CanInteract() && ServerChatRoomGetAllowItem(SenderCharacter, Player)) {
|
|
let misc = InventoryGet(Player, "ItemMisc");
|
|
if (KidnapLeagueSearchingPlayers.length == 0) {
|
|
if (misc && misc.Asset && misc.Asset.Name == "BountySuitcase") {
|
|
KidnapLeagueSearchFinishTime = CommonTime() + KidnapLeagueSearchFinishDuration;
|
|
ChatRoomPublishCustomAction("OnlineBountySuitcaseStart", true, [
|
|
{ Tag: "SourceCharacter", Text: SenderCharacter.Name, MemberNumber: SenderCharacter.MemberNumber },
|
|
{ Tag: "DestinationCharacterName", Text: Player.Name, MemberNumber: Player.MemberNumber },
|
|
]);
|
|
} else if (misc && misc.Asset && misc.Asset.Name == "BountySuitcaseEmpty") {
|
|
KidnapLeagueSearchFinishTime = CommonTime() + KidnapLeagueSearchFinishDuration;
|
|
ChatRoomPublishCustomAction("OnlineBountySuitcaseStartOpened", true, [
|
|
{ Tag: "SourceCharacter", Text: SenderCharacter.Name, MemberNumber: SenderCharacter.MemberNumber },
|
|
{ Tag: "DestinationCharacterName", Text: Player.Name, MemberNumber: Player.MemberNumber },
|
|
]);
|
|
}
|
|
} else {
|
|
ServerSend("ChatRoomGame", { OnlineBounty: {
|
|
finishTime: KidnapLeagueSearchFinishTime,
|
|
target: SenderCharacter.MemberNumber,
|
|
} });
|
|
}
|
|
if (!KidnapLeagueSearchingPlayers.includes(SenderCharacter.MemberNumber)) {
|
|
KidnapLeagueSearchingPlayers.push(SenderCharacter.MemberNumber);
|
|
}
|
|
|
|
}
|
|
}
|
|
else if (msg == "ReceiveSuitcaseMoney"){
|
|
ChatRoomReceiveSuitcaseMoney();
|
|
}
|
|
|
|
// If the message is still hidden after any modifications, stop processing
|
|
if (data.Type == "Hidden") return;
|
|
}
|
|
|
|
// Checks if the message is a notification about the user entering or leaving the room
|
|
var MsgEnterLeave = "";
|
|
var MsgNonDialogue = "";
|
|
if ((data.Type == "Action") && (msg.startsWith("ServerEnter") || msg.startsWith("ServerLeave") || msg.startsWith("ServerDisconnect") || msg.startsWith("ServerBan") || msg.startsWith("ServerKick")))
|
|
MsgEnterLeave = " ChatMessageEnterLeave";
|
|
if ((data.Type != "Chat" && data.Type != "Whisper" && data.Type != "Emote"))
|
|
MsgNonDialogue = " ChatMessageNonDialogue";
|
|
|
|
if (msg.startsWith("ServerDisconnect") && SenderCharacter.MemberNumber == ChatRoomLeashPlayer) ChatRoomLeashPlayer = null;
|
|
|
|
// Replace actions by the content of the dictionary
|
|
if (data.Type && ((data.Type == "Action") || (data.Type == "ServerMessage"))) {
|
|
if (data.Type == "ServerMessage") msg = "ServerMessage" + msg;
|
|
var orig_msg = msg;
|
|
msg = DialogFindPlayer(msg);
|
|
if (data.Dictionary) {
|
|
var dictionary = data.Dictionary;
|
|
var SourceCharacter = null;
|
|
let TargetCharacter = null;
|
|
var IsPlayerInvolved = (SenderCharacter.MemberNumber == Player.MemberNumber);
|
|
let TargetMemberNumber = null;
|
|
let ActivityName = null;
|
|
var GroupName = null;
|
|
let ActivityCounter = 1;
|
|
var Automatic = false;
|
|
var ShockIntensity = -1;
|
|
for (let D = 0; D < dictionary.length; D++) {
|
|
|
|
// If there's a member number in the dictionary packet, we use that number to alter the chat message
|
|
if (dictionary[D].MemberNumber) {
|
|
|
|
// Alters the message displayed in the chat room log, and stores the source & target in case they're required later
|
|
if ((dictionary[D].Tag == "DestinationCharacter") || (dictionary[D].Tag == "DestinationCharacterName")) {
|
|
TargetMemberNumber = dictionary[D].MemberNumber;
|
|
for (let T = 0; T < ChatRoomCharacter.length; T++)
|
|
if (ChatRoomCharacter[T].MemberNumber == dictionary[D].MemberNumber)
|
|
TargetCharacter = ChatRoomCharacter[T];
|
|
msg = msg.replace(dictionary[D].Tag, ((SenderCharacter.MemberNumber == dictionary[D].MemberNumber) && (dictionary[D].Tag == "DestinationCharacter")) ? DialogFindPlayer("Her") : (PreferenceIsPlayerInSensDep(ChatRoomSenseDepBypass) && dictionary[D].MemberNumber != Player.MemberNumber && (!ChatRoomSenseDepBypass || !ChatRoomCharacterDrawlist.includes(TargetCharacter)) ? DialogFindPlayer("Someone").toLowerCase() : ChatRoomHTMLEntities(dictionary[D].Text) + DialogFindPlayer("'s")));
|
|
|
|
}
|
|
else if ((dictionary[D].Tag == "TargetCharacter") || (dictionary[D].Tag == "TargetCharacterName")) {
|
|
TargetMemberNumber = dictionary[D].MemberNumber;
|
|
for (let T = 0; T < ChatRoomCharacter.length; T++)
|
|
if (ChatRoomCharacter[T].MemberNumber == dictionary[D].MemberNumber)
|
|
TargetCharacter = ChatRoomCharacter[T];
|
|
msg = msg.replace(dictionary[D].Tag, ((SenderCharacter.MemberNumber == dictionary[D].MemberNumber) && (dictionary[D].Tag == "TargetCharacter")) ? DialogFindPlayer("Herself") : (PreferenceIsPlayerInSensDep(ChatRoomSenseDepBypass) && dictionary[D].MemberNumber != Player.MemberNumber && (!ChatRoomSenseDepBypass || !ChatRoomCharacterDrawlist.includes(TargetCharacter)) ? DialogFindPlayer("Someone").toLowerCase() : ChatRoomHTMLEntities(dictionary[D].Text)));
|
|
|
|
}
|
|
else if (dictionary[D].Tag == "SourceCharacter") {
|
|
for (let T = 0; T < ChatRoomCharacter.length; T++)
|
|
if (ChatRoomCharacter[T].MemberNumber == dictionary[D].MemberNumber)
|
|
SourceCharacter = ChatRoomCharacter[T];
|
|
msg = msg.replace(dictionary[D].Tag, (PreferenceIsPlayerInSensDep(ChatRoomSenseDepBypass) && (dictionary[D].MemberNumber != Player.MemberNumber) && (!ChatRoomSenseDepBypass || !ChatRoomCharacterDrawlist.includes(SourceCharacter))) ? DialogFindPlayer("Someone") : ChatRoomHTMLEntities(dictionary[D].Text));
|
|
|
|
}
|
|
|
|
// Sets if the player is involved in the action
|
|
if (!IsPlayerInvolved && ((dictionary[D].Tag == "DestinationCharacter") || (dictionary[D].Tag == "DestinationCharacterName") || (dictionary[D].Tag == "TargetCharacter") || (dictionary[D].Tag == "TargetCharacterName") || (dictionary[D].Tag == "SourceCharacter" || dictionary[D].Tag === "ItemMemberNumber")))
|
|
if (dictionary[D].MemberNumber == Player.MemberNumber)
|
|
IsPlayerInvolved = true;
|
|
|
|
}
|
|
else if (dictionary[D].TextToLookUp) msg = msg.replace(dictionary[D].Tag, DialogFindPlayer(ChatRoomHTMLEntities(dictionary[D].TextToLookUp)).toLowerCase());
|
|
else if (dictionary[D].AssetName) {
|
|
for (let A = 0; A < Asset.length; A++)
|
|
if (Asset[A].Name == dictionary[D].AssetName) {
|
|
msg = msg.replace(dictionary[D].Tag, Asset[A].DynamicDescription(SourceCharacter || Player).toLowerCase());
|
|
ActivityName = Asset[A].DynamicActivity(SourceCharacter || Player);
|
|
break;
|
|
}
|
|
}
|
|
else if (dictionary[D].AssetGroupName) {
|
|
for (let A = 0; A < AssetGroup.length; A++)
|
|
if (AssetGroup[A].Name == dictionary[D].AssetGroupName) {
|
|
msg = msg.replace(dictionary[D].Tag, AssetGroup[A].Description.toLowerCase());
|
|
GroupName = dictionary[D].AssetGroupName;
|
|
}
|
|
}
|
|
else if (dictionary[D].ActivityCounter) ActivityCounter = dictionary[D].ActivityCounter;
|
|
else if (dictionary[D].Automatic) Automatic = true;
|
|
else if (dictionary[D].ShockIntensity != undefined) ShockIntensity = dictionary[D].ShockIntensity;
|
|
else if (msg != null) msg = msg.replace(dictionary[D].Tag, ChatRoomHTMLEntities(dictionary[D].Text));
|
|
}
|
|
|
|
// Trigger a shock if the player is a target
|
|
if (ShockIntensity >= 0 && TargetCharacter == Player) {
|
|
ChatRoomStimulationMessage("Flash", "#FFFFFF", ShockIntensity, 500);
|
|
}
|
|
|
|
// For automatic messages, do not show the message if the player is not involved, depending on their preferences
|
|
if (Automatic && !IsPlayerInvolved && !Player.ChatSettings.ShowAutomaticMessages) {
|
|
return;
|
|
}
|
|
|
|
// When the player is in total sensory deprivation, hide messages if the player is not involved
|
|
if (Player.ImmersionSettings.SenseDepMessages && !IsPlayerInvolved && PreferenceIsPlayerInSensDep()) {
|
|
return;
|
|
}
|
|
|
|
// Handle stimulation
|
|
if ((orig_msg == "HelpKneelDown" || orig_msg == "HelpStandUp") && ((TargetMemberNumber != null && TargetMemberNumber == Player.MemberNumber) || (SenderCharacter.MemberNumber != null && SenderCharacter.MemberNumber == Player.MemberNumber))) {
|
|
ChatRoomStimulationMessage("Kneel");
|
|
}
|
|
|
|
// If another player is using an item which applies an activity on the current player, apply the effect here
|
|
if ((ActivityName != null) && (TargetMemberNumber != null) && (TargetMemberNumber == Player.MemberNumber) && (SenderCharacter.MemberNumber != Player.MemberNumber))
|
|
if ((Player.ArousalSettings == null) || (Player.ArousalSettings.Active == null) || (Player.ArousalSettings.Active == "Hybrid") || (Player.ArousalSettings.Active == "Automatic"))
|
|
ActivityEffect(SenderCharacter, Player, ActivityName, GroupName, ActivityCounter);
|
|
|
|
// Launches the audio file if allowed
|
|
if (!Player.AudioSettings.PlayItemPlayerOnly || IsPlayerInvolved)
|
|
AudioPlayContent(data);
|
|
|
|
// Raise a notification if required
|
|
if (data.Type === "Action" && IsPlayerInvolved && Player.NotificationSettings.ChatMessage.Activity)
|
|
ChatRoomNotificationRaiseChatMessage(SenderCharacter, msg);
|
|
}
|
|
}
|
|
|
|
// Prepares the HTML tags
|
|
if (data.Type != null) {
|
|
const HideOthersMessages = Player.ImmersionSettings.SenseDepMessages
|
|
&& PreferenceIsPlayerInSensDep()
|
|
&& SenderCharacter.ID !== 0
|
|
&& Player.GetDeafLevel() >= 4
|
|
&& (!ChatRoomSenseDepBypass || !ChatRoomCharacterDrawlist.includes(SenderCharacter));
|
|
|
|
if (data.Type == "Chat" || data.Type == "Whisper") {
|
|
msg = '<span class="ChatMessageName" style="color:' + (SenderCharacter.LabelColor || 'gray');
|
|
if (data.Type == "Whisper") msg += '; font-style: italic';
|
|
msg += ';">';
|
|
|
|
// Garble names
|
|
let senderName = "";
|
|
if (PreferenceIsPlayerInSensDep(ChatRoomSenseDepBypass) && SenderCharacter.MemberNumber != Player.MemberNumber && data.Type != "Whisper" && (!ChatRoomSenseDepBypass || !ChatRoomCharacterDrawlist.includes(SenderCharacter))) {
|
|
if ((Player.GetDeafLevel() >= 4))
|
|
senderName = DialogFindPlayer("Someone");
|
|
else
|
|
senderName = SpeechGarble(SenderCharacter, SenderCharacter.Name, true);
|
|
} else {
|
|
senderName = SenderCharacter.Name;
|
|
}
|
|
msg += senderName;
|
|
msg += ':</span> ';
|
|
|
|
const chatMsg = ChatRoomHTMLEntities(data.Type === "Whisper" ? data.Content : SpeechGarble(SenderCharacter, data.Content));
|
|
msg += chatMsg;
|
|
ChatRoomChatLog.push({ Chat: SpeechGarble(SenderCharacter, data.Content, true), Garbled: chatMsg, Original: data.Content, SenderName: senderName, SenderMemberNumber: SenderCharacter.MemberNumber, Time: CommonTime() });
|
|
|
|
if (ChatRoomChatLog.length > 6) { // Keep it short
|
|
ChatRoomChatLog.splice(0, 1);
|
|
}
|
|
|
|
if (HideOthersMessages && data.Type === "Chat") {
|
|
return;
|
|
}
|
|
|
|
if ((data.Type === "Chat" && Player.NotificationSettings.ChatMessage.Normal)
|
|
|| (data.Type === "Whisper" && Player.NotificationSettings.ChatMessage.Whisper))
|
|
ChatRoomNotificationRaiseChatMessage(SenderCharacter, chatMsg);
|
|
}
|
|
else if (data.Type == "Emote") {
|
|
if (HideOthersMessages && !msg.toLowerCase().includes(Player.Name.toLowerCase())) {
|
|
return;
|
|
}
|
|
|
|
if (msg.indexOf("*") == 0) msg = msg + "*";
|
|
else if ((msg.indexOf("'") == 0) || (msg.indexOf(",") == 0)) msg = "*" + SenderCharacter.Name + msg + "*";
|
|
else if (PreferenceIsPlayerInSensDep(ChatRoomSenseDepBypass) && SenderCharacter.MemberNumber != Player.MemberNumber && (!ChatRoomSenseDepBypass || !ChatRoomCharacterDrawlist.includes(SenderCharacter))) {
|
|
msg = "*" + DialogFindPlayer("Someone") + " " + msg + "*";
|
|
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++) {
|
|
if (ChatRoomCharacter[C] && ChatRoomCharacter[C].Name && ChatRoomCharacter[C].ID != 0 && (!ChatRoomSenseDepBypass || !ChatRoomCharacterDrawlist.includes(ChatRoomCharacter[C])))
|
|
msg = msg.replace(ChatRoomCharacter[C].Name.charAt(0).toUpperCase() + ChatRoomCharacter[C].Name.slice(1), DialogFindPlayer("Someone"));
|
|
}
|
|
}
|
|
else msg = "*" + SenderCharacter.Name + " " + msg + "*";
|
|
|
|
if (Player.NotificationSettings.ChatMessage.Normal)
|
|
ChatRoomNotificationRaiseChatMessage(SenderCharacter, msg);
|
|
}
|
|
else if (data.Type == "Action") msg = "(" + msg + ")";
|
|
else if (data.Type == "ServerMessage") msg = "<b>" + msg + "</b>";
|
|
|
|
// Local messages can have HTML embedded in them
|
|
else if (data.Type == "LocalMessage") msg = data.Content;
|
|
}
|
|
|
|
// Outputs the sexual activities text and runs the activity if the player is targeted
|
|
if ((data.Type != null) && (data.Type === "Activity")) {
|
|
|
|
// Creates the output message using the activity dictionary and tags, keep some values to calculate the activity effects on the player
|
|
msg = "(" + ActivityDictionaryText(msg) + ")";
|
|
let TargetMemberNumber = null;
|
|
let TargetCharacter = null;
|
|
let ActivityName = null;
|
|
var ActivityGroup = null;
|
|
let ActivityCounter = 1;
|
|
if (data.Dictionary != null)
|
|
for (let D = 0; D < data.Dictionary.length; D++) {
|
|
for (let T = 0; T < ChatRoomCharacter.length; T++)
|
|
if (ChatRoomCharacter[T].MemberNumber == data.Dictionary[D].MemberNumber)
|
|
TargetCharacter = ChatRoomCharacter[T];
|
|
if (data.Dictionary[D].MemberNumber != null) {
|
|
msg = msg.replace(data.Dictionary[D].Tag, (PreferenceIsPlayerInSensDep(ChatRoomSenseDepBypass) && (data.Dictionary[D].MemberNumber != Player.MemberNumber) && (!ChatRoomSenseDepBypass || !ChatRoomCharacterDrawlist.includes(TargetCharacter))) ? DialogFindPlayer("Someone") : ChatRoomHTMLEntities(data.Dictionary[D].Text));
|
|
}
|
|
if ((data.Dictionary[D].MemberNumber != null) && (data.Dictionary[D].Tag == "TargetCharacter")) TargetMemberNumber = data.Dictionary[D].MemberNumber;
|
|
if (data.Dictionary[D].Tag == "ActivityName") ActivityName = data.Dictionary[D].Text;
|
|
if (data.Dictionary[D].Tag == "ActivityGroup") ActivityGroup = data.Dictionary[D].Text;
|
|
if (data.Dictionary[D].ActivityCounter != null) ActivityCounter = data.Dictionary[D].ActivityCounter;
|
|
}
|
|
|
|
|
|
|
|
// If the player does the activity on herself or an NPC, we calculate the result right away
|
|
if ((data.Type === "Action") || ((TargetMemberNumber == Player.MemberNumber) && (SenderCharacter.MemberNumber != Player.MemberNumber)))
|
|
if ((Player.ArousalSettings == null) || (Player.ArousalSettings.Active == null) || (Player.ArousalSettings.Active == "Hybrid") || (Player.ArousalSettings.Active == "Automatic"))
|
|
ActivityEffect(SenderCharacter, Player, ActivityName, ActivityGroup, ActivityCounter);
|
|
|
|
// When the player is in total sensory deprivation, hide messages if the player is not involved
|
|
if (Player.ImmersionSettings.SenseDepMessages && TargetMemberNumber != Player.MemberNumber && SenderCharacter.MemberNumber != Player.MemberNumber && PreferenceIsPlayerInSensDep()) {
|
|
return;
|
|
}
|
|
|
|
// Exits before outputting the text if the player doesn't want to see the sexual activity messages
|
|
if ((Player.ChatSettings != null) && (Player.ChatSettings.ShowActivities != null) && !Player.ChatSettings.ShowActivities) return;
|
|
|
|
// Raise a notification if required
|
|
if (TargetMemberNumber === Player.MemberNumber && Player.NotificationSettings.ChatMessage.Activity)
|
|
ChatRoomNotificationRaiseChatMessage(SenderCharacter, msg);
|
|
}
|
|
|
|
// Adds the message and scrolls down unless the user has scrolled up
|
|
var div = document.createElement("div");
|
|
div.setAttribute('class', 'ChatMessage ChatMessage' + data.Type + MsgEnterLeave + MsgNonDialogue);
|
|
div.setAttribute('data-time', ChatRoomCurrentTime());
|
|
div.setAttribute('data-sender', data.Sender);
|
|
if (data.Type == "Emote" || data.Type == "Action" || data.Type == "Activity")
|
|
div.setAttribute('style', 'background-color:' + ChatRoomGetTransparentColor(SenderCharacter.LabelColor) + ';');
|
|
div.innerHTML = msg;
|
|
|
|
if (typeof data.Timeout === 'number' && data.Timeout > 0) setTimeout(() => div.remove(), data.Timeout);
|
|
|
|
// Returns the focus on the chat box
|
|
var Refocus = document.activeElement.id == "InputChat";
|
|
var ShouldScrollDown = ElementIsScrolledToEnd("TextAreaChatLog");
|
|
if (document.getElementById("TextAreaChatLog") != null) {
|
|
document.getElementById("TextAreaChatLog").appendChild(div);
|
|
if (ShouldScrollDown) ElementScrollToEnd("TextAreaChatLog");
|
|
if (Refocus) ElementFocus("InputChat");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a character into the chat room.
|
|
* @param {Character} newCharacter - The new character to be added to the chat room.
|
|
* @param {object} newRawCharacter - The raw character data of the new character as it was received from the server.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomAddCharacterToChatRoom(newCharacter, newRawCharacter)
|
|
{
|
|
if(newCharacter == null) { return; }
|
|
if(newRawCharacter == null) { return; }
|
|
|
|
// Update the chat room characters
|
|
let characterIndex = ChatRoomCharacter.findIndex(x => x.MemberNumber == newCharacter.MemberNumber);
|
|
if(characterIndex >= 0) // If we found an existing entry...
|
|
{
|
|
// Update it
|
|
ChatRoomCharacter[characterIndex] = newCharacter;
|
|
}
|
|
else // If we didn't update existing data...
|
|
{
|
|
// Push a new entry
|
|
ChatRoomCharacter.push(newCharacter);
|
|
}
|
|
|
|
// Update chat room data backup
|
|
characterIndex = ChatRoomData.Character.findIndex(x => x.MemberNumber == newRawCharacter.MemberNumber);
|
|
if(characterIndex >= 0) // If we found an existing entry...
|
|
{
|
|
// Update it
|
|
ChatRoomData.Character[characterIndex] = newRawCharacter;
|
|
}
|
|
else // If we didn't update existing data...
|
|
{
|
|
// Push a new entry
|
|
ChatRoomData.Character.push(newRawCharacter);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Handles the reception of the complete room data from the server.
|
|
* @param {object} chatRoomProperties - Room object containing the updated chatroom data.
|
|
* @returns {boolean} - Returns true if the passed properties are valid and false if they're invalid.
|
|
*/
|
|
function ChatRoomValidateProperties(chatRoomProperties)
|
|
{
|
|
return chatRoomProperties != null && typeof chatRoomProperties === "object"
|
|
&& chatRoomProperties.Name != null && typeof chatRoomProperties.Name === "string"
|
|
&& chatRoomProperties.Description != null && typeof chatRoomProperties.Description === "string"
|
|
&& Array.isArray(chatRoomProperties.Admin)
|
|
&& Array.isArray(chatRoomProperties.Ban)
|
|
&& chatRoomProperties.Background != null && typeof chatRoomProperties.Background === "string"
|
|
&& chatRoomProperties.Limit != null && typeof chatRoomProperties.Limit === "number"
|
|
&& chatRoomProperties.Locked != null && typeof chatRoomProperties.Locked === "boolean"
|
|
&& chatRoomProperties.Private != null && typeof chatRoomProperties.Private === "boolean"
|
|
&& Array.isArray(chatRoomProperties.BlockCategory);
|
|
}
|
|
|
|
/**
|
|
* Handles the reception of the new room data from the server.
|
|
* @param {object} data - Room object containing the updated chatroom data.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSync(data) {
|
|
if (data == null || (typeof data !== "object")) {
|
|
return;
|
|
}
|
|
|
|
if(ChatRoomValidateProperties(data) == false) // If the room data we received is invalid...
|
|
{
|
|
// Instantly leave the chat room again
|
|
DialogLentLockpicks = false;
|
|
ChatRoomClearAllElements();
|
|
ChatRoomSetLastChatRoom("");
|
|
ServerSend("ChatRoomLeave", "");
|
|
ChatSearchMessage = "ErrorInvalidRoomProperties";
|
|
CommonSetScreen("Online", "ChatSearch");
|
|
return;
|
|
}
|
|
|
|
// Loads the room
|
|
if ((CurrentScreen != "ChatRoom") && (CurrentScreen != "ChatAdmin") && (CurrentScreen != "Appearance") && (CurrentModule != "Character")) {
|
|
if (ChatRoomPlayerCanJoin) {
|
|
ChatRoomPlayerCanJoin = false;
|
|
CommonSetScreen("Online", "ChatRoom");
|
|
} else return;
|
|
}
|
|
|
|
// Treat chatroom updates from ourselves as if the updated characters had sent them
|
|
const trustedUpdate = data.SourceMemberNumber === Player.MemberNumber;
|
|
|
|
// Load the characters
|
|
ChatRoomCharacter = [];
|
|
for (let C = 0; C < data.Character.length; C++) {
|
|
const sourceMemberNumber = trustedUpdate ? data.Character[C].MemberNumber : data.SourceMemberNumber;
|
|
const Char = CharacterLoadOnline(data.Character[C], sourceMemberNumber);
|
|
ChatRoomCharacter.push(Char);
|
|
}
|
|
|
|
// Keeps a copy of the previous version
|
|
ChatRoomData = data;
|
|
if (ChatRoomData.Game != null) {
|
|
ChatRoomGame = ChatRoomData.Game;
|
|
}
|
|
|
|
// Check whether the player's last chatroom data needs updating
|
|
ChatRoomCheckForLastChatRoomUpdates();
|
|
|
|
// Reloads the online game statuses if needed
|
|
OnlineGameLoadStatus();
|
|
|
|
// The allowed menu actions may have changed
|
|
ChatRoomMenuBuild();
|
|
}
|
|
|
|
|
|
/**
|
|
* Handles the reception of the character data of a single player from the server.
|
|
* @param {object} data - object containing the character's data.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncCharacter(data) {
|
|
if (data == null || (typeof data !== "object")) {
|
|
return;
|
|
}
|
|
|
|
const newCharacter = CharacterLoadOnline(data.Character, data.SourceMemberNumber);
|
|
ChatRoomAddCharacterToChatRoom(newCharacter, data.Character);
|
|
|
|
}
|
|
|
|
/**
|
|
* Handles the reception of the character data of a newly joined player from the server.
|
|
* @param {object} data - object containing the joined character's data.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncMemberJoin(data) {
|
|
if (data == null || (typeof data !== "object")) {
|
|
return;
|
|
}
|
|
|
|
//Load the character to the chat room
|
|
const newCharacter = CharacterLoadOnline(data.Character, data.SourceMemberNumber);
|
|
ChatRoomAddCharacterToChatRoom(newCharacter, data.Character);
|
|
|
|
if (Array.isArray(data.WhiteListedBy)) {
|
|
for (const MemberNumber of data.WhiteListedBy) {
|
|
for (const character of Character) {
|
|
if (character.MemberNumber === MemberNumber && Array.isArray(character.WhiteList) && character.ID != 0) {
|
|
if (!character.WhiteList.includes(newCharacter.MemberNumber)) {
|
|
character.WhiteList.push(newCharacter.MemberNumber);
|
|
character.WhiteList.sort((a, b) => a - b);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (Array.isArray(data.BlackListedBy)) {
|
|
for (const MemberNumber of data.BlackListedBy) {
|
|
for (const character of Character) {
|
|
if (character.MemberNumber === MemberNumber && Array.isArray(character.BlackList) && character.ID != 0) {
|
|
if (!character.BlackList.includes(newCharacter.MemberNumber)) {
|
|
character.BlackList.push(newCharacter.MemberNumber);
|
|
character.BlackList.sort((a, b) => a - b);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// After Join Actions
|
|
if (ChatRoomNotificationRaiseChatJoin(newCharacter)) {
|
|
NotificationRaise(NotificationEventType.CHATJOIN, { characterName: newCharacter.Name });
|
|
}
|
|
if (ChatRoomLeashList.includes(newCharacter.MemberNumber)) {
|
|
// Ping to make sure they are still leashed
|
|
ServerSend("ChatRoomChat", { Content: "PingHoldLeash", Type: "Hidden", Target: newCharacter.MemberNumber });
|
|
}
|
|
// Check whether the player's last chatroom data needs updating
|
|
ChatRoomCheckForLastChatRoomUpdates();
|
|
// The allowed menu actions may have changed
|
|
ChatRoomMenuBuild();
|
|
|
|
}
|
|
|
|
/**
|
|
* Handles the reception of the leave notification of a player from the server.
|
|
* @param {object} data - Room object containing the leaving character's member number.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncMemberLeave(data) {
|
|
if (data == null || (typeof data !== "object")) {
|
|
return;
|
|
}
|
|
|
|
// Remove the character
|
|
ChatRoomCharacter = ChatRoomCharacter.filter(x => x.MemberNumber != data.SourceMemberNumber);
|
|
ChatRoomData.Character = ChatRoomData.Character.filter(x => x.MemberNumber != data.SourceMemberNumber);
|
|
|
|
// Check whether the player's last chatroom data needs updating
|
|
ChatRoomCheckForLastChatRoomUpdates();
|
|
|
|
// The allowed menu actions may have changed
|
|
ChatRoomMenuBuild();
|
|
|
|
}
|
|
|
|
/**
|
|
* Handles the reception of the room properties from the server.
|
|
* @param {object} data - Room object containing the updated chatroom properties.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncRoomProperties(data) {
|
|
if (data == null || (typeof data !== "object")) {
|
|
return;
|
|
}
|
|
|
|
if(ChatRoomValidateProperties(data) == false) // If the room data we received is invalid...
|
|
{
|
|
// Instantly leave the chat room again
|
|
DialogLentLockpicks = false;
|
|
ChatRoomClearAllElements();
|
|
ChatRoomSetLastChatRoom("");
|
|
ServerSend("ChatRoomLeave", "");
|
|
ChatSearchMessage = "ErrorInvalidRoomProperties";
|
|
CommonSetScreen("Online", "ChatSearch");
|
|
return;
|
|
}
|
|
|
|
// Copy the received properties to chat room data
|
|
Object.assign(ChatRoomData, data);
|
|
|
|
if (ChatRoomData.Game != null) ChatRoomGame = ChatRoomData.Game;
|
|
|
|
// Check whether the player's last chatroom data needs updating
|
|
ChatRoomCheckForLastChatRoomUpdates();
|
|
|
|
// Reloads the online game statuses if needed
|
|
OnlineGameLoadStatus();
|
|
|
|
// The allowed menu actions may have changed
|
|
ChatRoomMenuBuild();
|
|
|
|
}
|
|
|
|
/**
|
|
* Handles the swapping of two players by a room administrator.
|
|
* @param {object} data - Object containing the member numbers of the swapped characters.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncSwapPlayers(data) {
|
|
if (data == null || (typeof data !== "object")) {
|
|
return;
|
|
}
|
|
|
|
// Update the chat room characters
|
|
let index1 = ChatRoomCharacter.findIndex(x => (x.MemberNumber == data.MemberNumber1));
|
|
let index2 = ChatRoomCharacter.findIndex(x => (x.MemberNumber == data.MemberNumber2));
|
|
|
|
if(index1 >= 0 && index2 >= 0) // If we found both characters to swap...
|
|
{
|
|
//Swap them
|
|
let bufferCharacter = ChatRoomCharacter[index1];
|
|
ChatRoomCharacter[index1] = ChatRoomCharacter[index2];
|
|
ChatRoomCharacter[index2] = bufferCharacter;
|
|
}
|
|
|
|
// Update the chat room data backup
|
|
index1 = ChatRoomData.Character.findIndex(x => x.MemberNumber == data.MemberNumber1);
|
|
index2 = ChatRoomData.Character.findIndex(x => x.MemberNumber == data.MemberNumber2);
|
|
|
|
if(index1 >= 0 && index2 >= 0) // If we found both entries to swap...
|
|
{
|
|
//Swap them
|
|
let bufferCharacter = ChatRoomData.Character[index1];
|
|
ChatRoomData.Character[index1] = ChatRoomData.Character[index2];
|
|
ChatRoomData.Character[index2] = bufferCharacter;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Handles the moving of a player by a room administrator.
|
|
* @param {object} data - Object containing the member numbers of the swapped characters.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncMovePlayer(data) {
|
|
if (data == null || (typeof data !== "object")) {
|
|
return;
|
|
}
|
|
|
|
let moveOffset = 0;
|
|
switch(data.Direction)
|
|
{
|
|
case "Left": moveOffset = -1; break;
|
|
case "Right": moveOffset = 1; break;
|
|
default: moveOffset = 0; break;
|
|
}
|
|
|
|
// Update the chat room characters
|
|
let index = ChatRoomCharacter.findIndex(x => x.MemberNumber == data.TargetMemberNumber);
|
|
if(index >= 0 && index < ChatRoomCharacter.length &&
|
|
index+moveOffset >= 0 && index+moveOffset < ChatRoomCharacter.length) // If we found the character to move and the moving is valid...
|
|
{
|
|
//Move it
|
|
let bufferCharacter = ChatRoomCharacter[index];
|
|
ChatRoomCharacter[index] = ChatRoomCharacter[index+moveOffset];
|
|
ChatRoomCharacter[index+moveOffset] = bufferCharacter;
|
|
}
|
|
|
|
// Update the chat room data backup
|
|
index = ChatRoomData.Character.findIndex(x => x.MemberNumber == data.TargetMemberNumber);
|
|
if(index >= 0 && index < ChatRoomCharacter.length &&
|
|
index+moveOffset >= 0 && index+moveOffset < ChatRoomCharacter.length) // If we found the entry to move and the moving is valid...
|
|
{
|
|
//Move it
|
|
let bufferCharacter = ChatRoomData.Character[index];
|
|
ChatRoomData.Character[index] = ChatRoomData.Character[index+moveOffset];
|
|
ChatRoomData.Character[index+moveOffset] = bufferCharacter;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Handles the swapping of two players by a room administrator.
|
|
* @param {object} data - Object containing the member numbers of the swapped characters.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncReorderPlayers(data) {
|
|
if (data == null || (typeof data !== "object")) {
|
|
return;
|
|
}
|
|
|
|
let newChatRoomCharacter = [];
|
|
let newChatRoomDataCharacter = [];
|
|
let index = 0;
|
|
|
|
for(let i=0; i<data.PlayerOrder.length; i++) // For every player to reorder...
|
|
{
|
|
//Chat Room Characters
|
|
index = ChatRoomCharacter.findIndex(x => x.MemberNumber == data.PlayerOrder[i]);
|
|
newChatRoomCharacter.push(ChatRoomCharacter.splice(index, 1)[0]);
|
|
|
|
//Chat Room Data Backup
|
|
index = ChatRoomData.Character.findIndex(x => x.MemberNumber == data.PlayerOrder[i]);
|
|
newChatRoomDataCharacter.push(ChatRoomData.Character.splice(index, 1)[0]);
|
|
|
|
}
|
|
|
|
if(ChatRoomCharacter.length > 0) // If we forgot about some characters for some reason...
|
|
{
|
|
//Push the missed entries to the end
|
|
Array.prototype.push.apply(newChatRoomCharacter, ChatRoomCharacter);
|
|
}
|
|
if(ChatRoomData.Character.length > 0) // If we forgot about some entries for some reason...
|
|
{
|
|
//Push the missed entries to the end
|
|
Array.prototype.push.apply(newChatRoomDataCharacter, ChatRoomData.Character);
|
|
}
|
|
|
|
//Update the origin arrays
|
|
ChatRoomCharacter = newChatRoomCharacter;
|
|
ChatRoomData.Character = newChatRoomDataCharacter;
|
|
|
|
}
|
|
|
|
/**
|
|
* Updates a single character in the chatroom
|
|
* @param {object} data - Data object containing the new character data.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncSingle(data) {
|
|
|
|
// Sets the chat room character data
|
|
if ((data == null) || (typeof data !== "object")) return;
|
|
if ((data.Character == null) || (typeof data.Character !== "object")) return;
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++)
|
|
if (ChatRoomCharacter[C].MemberNumber == data.Character.MemberNumber)
|
|
ChatRoomCharacter[C] = CharacterLoadOnline(data.Character, data.SourceMemberNumber);
|
|
|
|
// Keeps a copy of the previous version
|
|
for (let C = 0; C < ChatRoomData.Character.length; C++)
|
|
if (ChatRoomData.Character[C].MemberNumber == data.Character.MemberNumber)
|
|
ChatRoomData.Character[C] = data.Character;
|
|
|
|
}
|
|
|
|
/**
|
|
* Updates a single character's expression in the chatroom.
|
|
* @param {object} data - Data object containing the new character expression data.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncExpression(data) {
|
|
if ((data == null) || (typeof data !== "object") || (data.Group == null) || (typeof data.Group !== "string")) return;
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++)
|
|
if (ChatRoomCharacter[C].MemberNumber == data.MemberNumber) {
|
|
|
|
// Changes the facial expression
|
|
for (let A = 0; A < ChatRoomCharacter[C].Appearance.length; A++)
|
|
if ((ChatRoomCharacter[C].Appearance[A].Asset.Group.Name == data.Group) && (ChatRoomCharacter[C].Appearance[A].Asset.Group.AllowExpression))
|
|
if ((data.Name == null) || (ChatRoomCharacter[C].Appearance[A].Asset.Group.AllowExpression.indexOf(data.Name) >= 0)) {
|
|
if (!ChatRoomCharacter[C].Appearance[A].Property) ChatRoomCharacter[C].Appearance[A].Property = {};
|
|
if (ChatRoomCharacter[C].Appearance[A].Property.Expression != data.Name) {
|
|
ChatRoomCharacter[C].Appearance[A].Property.Expression = data.Name;
|
|
CharacterRefresh(ChatRoomCharacter[C], false);
|
|
}
|
|
}
|
|
|
|
// Keeps a copy of the previous version
|
|
for (let C = 0; C < ChatRoomData.Character.length; C++)
|
|
if (ChatRoomData.Character[C].MemberNumber == data.MemberNumber)
|
|
ChatRoomData.Character[C].Appearance = ChatRoomCharacter[C].Appearance;
|
|
return;
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates a single character's pose in the chatroom.
|
|
* @param {object} data - Data object containing the new character pose data.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncPose(data) {
|
|
if ((data == null) || (typeof data !== "object")) return;
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++)
|
|
if (ChatRoomCharacter[C].MemberNumber == data.MemberNumber) {
|
|
|
|
// Sets the active pose
|
|
ChatRoomCharacter[C].ActivePose = data.Pose;
|
|
CharacterRefresh(ChatRoomCharacter[C], false);
|
|
|
|
// Keeps a copy of the previous version
|
|
for (let C = 0; C < ChatRoomData.Character.length; C++)
|
|
if (ChatRoomData.Character[C].MemberNumber == data.MemberNumber)
|
|
ChatRoomData.Character[C].ActivePose = data.Pose;
|
|
return;
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates a single character's arousal progress in the chatroom.
|
|
* @param {object} data - Data object containing the new character arousal data.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncArousal(data) {
|
|
if ((data == null) || (typeof data !== "object")) return;
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++)
|
|
if ((ChatRoomCharacter[C].MemberNumber == data.MemberNumber) && (ChatRoomCharacter[C].ArousalSettings != null)) {
|
|
|
|
// Sets the orgasm count & progress
|
|
ChatRoomCharacter[C].ArousalSettings.OrgasmTimer = data.OrgasmTimer;
|
|
ChatRoomCharacter[C].ArousalSettings.OrgasmCount = data.OrgasmCount;
|
|
ChatRoomCharacter[C].ArousalSettings.Progress = data.Progress;
|
|
ChatRoomCharacter[C].ArousalSettings.ProgressTimer = data.ProgressTimer;
|
|
if ((ChatRoomCharacter[C].ArousalSettings.AffectExpression == null) || ChatRoomCharacter[C].ArousalSettings.AffectExpression) ActivityExpression(ChatRoomCharacter[C], ChatRoomCharacter[C].ArousalSettings.Progress);
|
|
|
|
// Keeps a copy of the previous version
|
|
for (let C = 0; C < ChatRoomData.Character.length; C++)
|
|
if (ChatRoomData.Character[C].MemberNumber == data.MemberNumber) {
|
|
ChatRoomData.Character[C].ArousalSettings.OrgasmTimer = data.OrgasmTimer;
|
|
ChatRoomData.Character[C].ArousalSettings.OrgasmCount = data.OrgasmCount;
|
|
ChatRoomData.Character[C].ArousalSettings.Progress = data.Progress;
|
|
ChatRoomData.Character[C].ArousalSettings.ProgressTimer = data.ProgressTimer;
|
|
ChatRoomData.Character[C].Appearance = ChatRoomCharacter[C].Appearance;
|
|
}
|
|
return;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Updates a single item on a specific character in the chatroom.
|
|
* @param {object} data - Data object containing the data pertaining to the singular item to update.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomSyncItem(data) {
|
|
if ((data == null) || (typeof data !== "object") || (data.Source == null) || (typeof data.Source !== "number") || (data.Item == null) || (typeof data.Item !== "object") || (data.Item.Target == null) || (typeof data.Item.Target !== "number") || (data.Item.Group == null) || (typeof data.Item.Group !== "string")) return;
|
|
for (let C = 0; C < ChatRoomCharacter.length; C++)
|
|
if (ChatRoomCharacter[C].MemberNumber === data.Item.Target) {
|
|
|
|
const updateParams = ValidationCreateDiffParams(ChatRoomCharacter[C], data.Source);
|
|
const previousItem = InventoryGet(ChatRoomCharacter[C], data.Item.Group);
|
|
const newItem = ServerBundledItemToAppearanceItem(ChatRoomCharacter[C].AssetFamily, data.Item);
|
|
|
|
let { item, valid } = ValidationResolveAppearanceDiff(previousItem, newItem, updateParams);
|
|
|
|
ChatRoomAllowCharacterUpdate = false;
|
|
if (item) {
|
|
CharacterAppearanceSetItem(
|
|
ChatRoomCharacter[C], data.Item.Group, item.Asset, item.Color, item.Difficulty, null, false);
|
|
|
|
InventoryGet(ChatRoomCharacter[C], data.Item.Group).Property = item.Property;
|
|
|
|
/** @type {AppearanceDiffMap} */
|
|
const diffMap = {};
|
|
for (const appearanceItem of ChatRoomCharacter[C].Appearance) {
|
|
const groupName = appearanceItem.Asset.Group.Name;
|
|
if (groupName === data.Item.Group) {
|
|
diffMap[groupName] = [previousItem, appearanceItem];
|
|
} else {
|
|
diffMap[groupName] = [appearanceItem, appearanceItem];
|
|
}
|
|
}
|
|
|
|
const cyclicBlockSanitizationResult = ValidationResolveCyclicBlocks(ChatRoomCharacter[C].Appearance, diffMap);
|
|
ChatRoomCharacter[C].Appearance = cyclicBlockSanitizationResult.appearance;
|
|
valid = valid && cyclicBlockSanitizationResult.valid;
|
|
} else {
|
|
InventoryRemove(ChatRoomCharacter[C], data.Item.Group);
|
|
}
|
|
|
|
ChatRoomAllowCharacterUpdate = true;
|
|
|
|
// If the update was invalid, send a correction update
|
|
if (ChatRoomCharacter[C].ID === 0 && !valid) {
|
|
console.warn(`Invalid appearance update to group ${data.Item.Group}. Updating with sanitized appearance.`);
|
|
ChatRoomCharacterUpdate(ChatRoomCharacter[C]);
|
|
} else {
|
|
CharacterRefresh(ChatRoomCharacter[C]);
|
|
}
|
|
|
|
// Keeps the change in the chat room data and allows the character to be updated again
|
|
for (let R = 0; R < ChatRoomData.Character.length; R++) {
|
|
if (ChatRoomData.Character[R].MemberNumber == data.Item.Target)
|
|
ChatRoomData.Character[R].Appearance = ChatRoomCharacter[C].Appearance;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Refreshes the chat log elements for Player
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomRefreshChatSettings() {
|
|
if (Player.ChatSettings) {
|
|
for (let property in Player.ChatSettings)
|
|
ElementSetDataAttribute("TextAreaChatLog", property, Player.ChatSettings[property]);
|
|
if (Player.GameplaySettings &&
|
|
(Player.GameplaySettings.SensDepChatLog == "SensDepNames" || Player.GameplaySettings.SensDepChatLog == "SensDepTotal" || Player.GameplaySettings.SensDepChatLog == "SensDepExtreme") &&
|
|
(Player.GetDeafLevel() >= 3) &&
|
|
(Player.GetBlindLevel() >= 3)) {
|
|
ElementSetDataAttribute("TextAreaChatLog", "EnterLeave", "Hidden");
|
|
}
|
|
if (Player.GameplaySettings && (Player.GameplaySettings.SensDepChatLog == "SensDepTotal" || Player.GameplaySettings.SensDepChatLog == "SensDepExtreme") && (Player.GetDeafLevel() >= 3) && (Player.GetBlindLevel() >= 3)) {
|
|
ElementSetDataAttribute("TextAreaChatLog", "DisplayTimestamps", "false");
|
|
ElementSetDataAttribute("TextAreaChatLog", "ColorNames", "false");
|
|
ElementSetDataAttribute("TextAreaChatLog", "ColorActions", "false");
|
|
ElementSetDataAttribute("TextAreaChatLog", "ColorEmotes", "false");
|
|
ElementSetDataAttribute("TextAreaChatLog", "ColorActivities", "false");
|
|
ElementSetDataAttribute("TextAreaChatLog", "MemberNumbers", "Never");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shows the current character's profile (Information Sheet screen)
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomViewProfile() {
|
|
if (CurrentCharacter != null) {
|
|
var C = CurrentCharacter;
|
|
DialogLeave();
|
|
InformationSheetLoadCharacter(C);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Brings the player into the main hall and starts the maid punishment sequence
|
|
* @returns {void}
|
|
*/
|
|
function DialogCallMaids() {
|
|
ChatRoomSlowtimer = 0;
|
|
ChatRoomSlowStop = false;
|
|
ChatRoomClearAllElements();
|
|
ChatRoomSetLastChatRoom("");
|
|
ServerSend("ChatRoomLeave", "");
|
|
CommonSetScreen("Room", "MainHall");
|
|
if (!Player.RestrictionSettings || !Player.RestrictionSettings.BypassNPCPunishments) {
|
|
MainHallPunishFromChatroom();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Triggered when the player assists another player to struggle out, the bonus is evasion / 2 + 1, with penalties if
|
|
* the player is restrained.
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomStruggleAssist() {
|
|
var Dictionary = [];
|
|
Dictionary.push({ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber });
|
|
Dictionary.push({ Tag: "TargetCharacter", Text: CurrentCharacter.Name, MemberNumber: CurrentCharacter.MemberNumber });
|
|
var Bonus = SkillGetLevelReal(Player, "Evasion") / 2 + 1;
|
|
if (!Player.CanInteract()) {
|
|
if (InventoryItemHasEffect(InventoryGet(Player, "ItemArms"), "Block", true)) Bonus = Bonus / 1.5;
|
|
if (InventoryItemHasEffect(InventoryGet(Player, "ItemHands"), "Block", true)) Bonus = Bonus / 1.5;
|
|
if (!Player.CanTalk()) Bonus = Bonus / 1.25;
|
|
}
|
|
ServerSend("ChatRoomChat", { Content: "StruggleAssist", Type: "Action", Dictionary: Dictionary });
|
|
ServerSend("ChatRoomChat", { Content: "StruggleAssist" + Math.round(Bonus).toString(), Type: "Hidden", Target: CurrentCharacter.MemberNumber });
|
|
DialogLeave();
|
|
}
|
|
|
|
/**
|
|
|
|
* Triggered when the player assists another player to by giving lockpicks
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomGiveLockpicks() {
|
|
var Dictionary = [];
|
|
Dictionary.push({ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber });
|
|
Dictionary.push({ Tag: "TargetCharacter", Text: CurrentCharacter.Name, MemberNumber: CurrentCharacter.MemberNumber });
|
|
ServerSend("ChatRoomChat", { Content: "GiveLockpicks", Type: "Action", Dictionary: Dictionary });
|
|
ServerSend("ChatRoomChat", { Content: "GiveLockpicks", Type: "Hidden", Target: CurrentCharacter.MemberNumber });
|
|
DialogLeave();
|
|
}
|
|
|
|
/*
|
|
* Triggered when the player grabs another player's leash
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomHoldLeash() {
|
|
var Dictionary = [];
|
|
Dictionary.push({ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber });
|
|
Dictionary.push({ Tag: "TargetCharacter", Text: CurrentCharacter.Name, MemberNumber: CurrentCharacter.MemberNumber });
|
|
ServerSend("ChatRoomChat", { Content: "HoldLeash", Type: "Action", Dictionary: Dictionary });
|
|
ServerSend("ChatRoomChat", { Content: "HoldLeash", Type: "Hidden", Target: CurrentCharacter.MemberNumber });
|
|
if (ChatRoomLeashList.indexOf(CurrentCharacter.MemberNumber) < 0)
|
|
ChatRoomLeashList.push(CurrentCharacter.MemberNumber);
|
|
DialogLeave();
|
|
}
|
|
|
|
/**
|
|
* Triggered when the player lets go of another player's leash
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomStopHoldLeash() {
|
|
var Dictionary = [];
|
|
Dictionary.push({ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber });
|
|
Dictionary.push({ Tag: "TargetCharacter", Text: CurrentCharacter.Name, MemberNumber: CurrentCharacter.MemberNumber });
|
|
ServerSend("ChatRoomChat", { Content: "StopHoldLeash", Type: "Action", Dictionary: Dictionary });
|
|
ServerSend("ChatRoomChat", { Content: "StopHoldLeash", Type: "Hidden", Target: CurrentCharacter.MemberNumber });
|
|
if (ChatRoomLeashList.indexOf(CurrentCharacter.MemberNumber) >= 0)
|
|
ChatRoomLeashList.splice(ChatRoomLeashList.indexOf(CurrentCharacter.MemberNumber), 1);
|
|
DialogLeave();
|
|
}
|
|
|
|
/**
|
|
* Triggered when a dom enters the room
|
|
* @returns {void} - Nothing.
|
|
*/
|
|
function ChatRoomPingLeashedPlayers(NoBeep) {
|
|
if (ChatRoomLeashList && ChatRoomLeashList.length > 0) {
|
|
for (let P = 0; P < ChatRoomLeashList.length; P++) {
|
|
ServerSend("ChatRoomChat", { Content: "PingHoldLeash", Type: "Hidden", Target: ChatRoomLeashList[P] });
|
|
ServerSend("AccountBeep", { MemberNumber: ChatRoomLeashList[P], BeepType:"Leash"});
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Triggered when a character makes another character kneel/stand.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomKneelStandAssist() {
|
|
ServerSend("ChatRoomChat", { Content: !CurrentCharacter.IsKneeling() ? "HelpKneelDown" : "HelpStandUp", Type: "Action", Dictionary: [{ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber }, { Tag: "TargetCharacter", Text: CurrentCharacter.Name, MemberNumber: CurrentCharacter.MemberNumber }] });
|
|
CharacterSetActivePose(CurrentCharacter, !CurrentCharacter.IsKneeling() ? "Kneel" : null, true);
|
|
ChatRoomCharacterUpdate(CurrentCharacter);
|
|
}
|
|
|
|
/**
|
|
* Triggered when a character stops another character from leaving.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomStopLeave(){
|
|
var Dictionary = [];
|
|
Dictionary.push({Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber});
|
|
Dictionary.push({Tag: "TargetCharacter", Text: CurrentCharacter.Name, MemberNumber: CurrentCharacter.MemberNumber});
|
|
ServerSend("ChatRoomChat", { Content: "SlowStop", Type: "Action", Dictionary: Dictionary});
|
|
ServerSend("ChatRoomChat", { Content: "SlowStop", Type: "Hidden", Target: CurrentCharacter.MemberNumber } );
|
|
DialogLeave();
|
|
}
|
|
|
|
/**
|
|
* Sends an administrative command to the server for the chat room from the character dialog.
|
|
* @param {string} ActionType - Type of action performed.
|
|
* @param {boolean | string} [Publish=true] - Whether or not the action should be published.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomAdminAction(ActionType, Publish) {
|
|
if ((CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && ChatRoomPlayerIsAdmin()) {
|
|
if (ActionType == "Move") {
|
|
ChatRoomMoveTarget = CurrentCharacter.MemberNumber;
|
|
} else {
|
|
ServerSend("ChatRoomAdmin", { MemberNumber: CurrentCharacter.MemberNumber, Action: ActionType, Publish: ((Publish == null) || (Publish != false && Publish != "false")) });
|
|
}
|
|
DialogLeave();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sends an administrative command to the server from the chat text field.
|
|
* @param {string} ActionType - Type of action performed.
|
|
* @param {string} Argument - Target number of the action.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomAdminChatAction(ActionType, Argument ) {
|
|
if (ChatRoomPlayerIsAdmin()) {
|
|
var C = parseInt(Argument);
|
|
if (!isNaN(C) && (C > 0) && (C != Player.MemberNumber))
|
|
ServerSend("ChatRoomAdmin", { MemberNumber: C, Action: ActionType });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the player's current time as a string.
|
|
* @returns {string} - The player's current local time as a string.
|
|
*/
|
|
function ChatRoomCurrentTime() {
|
|
var D = new Date();
|
|
return ("0" + D.getHours()).substr(-2) + ":" + ("0" + D.getMinutes()).substr(-2);
|
|
}
|
|
|
|
/**
|
|
* Gets a transparent version of the specified hex color.
|
|
* @param {HexColor} Color - Hex color code.
|
|
* @returns {string} - A transparent version of the specified hex color in the rgba format.
|
|
*/
|
|
function ChatRoomGetTransparentColor(Color) {
|
|
if (!Color) return "rgba(128,128,128,0.1)";
|
|
var R = Color.substring(1, 3), G = Color.substring(3, 5), B = Color.substring(5, 7);
|
|
return "rgba(" + parseInt(R, 16) + "," + parseInt(G, 16) + "," + parseInt(B, 16) + ",0.1)";
|
|
}
|
|
|
|
/**
|
|
* Adds or removes an online member to/from a specific list. (From the dialog menu)
|
|
* @param {"Add" | "Remove"} Operation - Operation to perform.
|
|
* @param {string} ListType - Name of the list to alter. (Whitelist, friendlist, blacklist, ghostlist)
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomListManage(Operation, ListType) {
|
|
if (((Operation == "Add" || Operation == "Remove")) && (CurrentCharacter != null) && (CurrentCharacter.MemberNumber != null) && (Player[ListType] != null) && Array.isArray(Player[ListType])) {
|
|
if ((Operation == "Add") && (Player[ListType].indexOf(CurrentCharacter.MemberNumber) < 0)) Player[ListType].push(CurrentCharacter.MemberNumber);
|
|
if ((Operation == "Remove") && (Player[ListType].indexOf(CurrentCharacter.MemberNumber) >= 0)) Player[ListType].splice(Player[ListType].indexOf(CurrentCharacter.MemberNumber), 1);
|
|
ServerPlayerRelationsSync();
|
|
setTimeout(() => ChatRoomCharacterUpdate(Player), 5000);
|
|
}
|
|
if (ListType == "GhostList") {
|
|
CharacterRefresh(CurrentCharacter, false);
|
|
ChatRoomListManage(Operation, "BlackList");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds or removes an online member to/from a specific list. (From a typed message.)
|
|
* @param {number[]|null} Add - List to add to.
|
|
* @param {number[]|null} Remove - List to remove from.
|
|
* @param {string} Argument - Member number to add/remove.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomListManipulation(Add, Remove, Argument) {
|
|
var C = parseInt(Argument);
|
|
if (!isNaN(C) && (C > 0) && (C != Player.MemberNumber)) {
|
|
if ((Add != null) && (Add.indexOf(C) < 0)) Add.push(C);
|
|
if ((Remove != null) && (Remove.indexOf(C) >= 0)) Remove.splice(Remove.indexOf(C), 1);
|
|
if ((Player.GhostList == Add || Player.GhostList == Remove) && Character.find(Char => Char.MemberNumber == C)) CharacterRefresh(Character.find(Char => Char.MemberNumber == C), false);
|
|
ServerPlayerRelationsSync();
|
|
setTimeout(() => ChatRoomCharacterUpdate(Player), 5000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles reception of data pertaining to if applying an item is allowed.
|
|
* @param {object} data - Data object containing if the player is allowed to interact with a character.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomAllowItem(data) {
|
|
if ((data != null) && (typeof data === "object") && (data.MemberNumber != null) && (typeof data.MemberNumber === "number") && (data.AllowItem != null) && (typeof data.AllowItem === "boolean"))
|
|
if (CurrentCharacter != null && CurrentCharacter.MemberNumber == data.MemberNumber && data.AllowItem !== CurrentCharacter.AllowItem) {
|
|
console.warn(`ChatRoomGetAllowItem mismatch trying to access ${CurrentCharacter.Name} (${CurrentCharacter.MemberNumber})`);
|
|
CurrentCharacter.AllowItem = data.AllowItem;
|
|
CharacterSetCurrent(CurrentCharacter);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggered when the player wants to change another player's outfit.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomChangeClothes() {
|
|
var C = CurrentCharacter;
|
|
DialogLeave();
|
|
CharacterAppearanceLoadCharacter(C);
|
|
}
|
|
|
|
/**
|
|
* Triggered when the player selects an ownership dialog option. (It can change money and reputation)
|
|
* @param {string} RequestType - Type of request being performed.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomSendOwnershipRequest(RequestType) {
|
|
if ((ChatRoomOwnershipOption == "CanOfferEndTrial") && (RequestType == "Propose")) { CharacterChangeMoney(Player, -100); DialogChangeReputation("Dominant", 10); }
|
|
if ((ChatRoomOwnershipOption == "CanEndTrial") && (RequestType == "Accept")) DialogChangeReputation("Dominant", -20);
|
|
ChatRoomOwnershipOption = "";
|
|
ServerSend("AccountOwnership", { MemberNumber: CurrentCharacter.MemberNumber, Action: RequestType });
|
|
if (RequestType == "Accept") DialogLeave();
|
|
}
|
|
|
|
/**
|
|
* Triggered when the player selects an lovership dialog option. (It can change money and reputation)
|
|
* @param {string} RequestType - Type of request being performed.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomSendLovershipRequest(RequestType) {
|
|
if ((ChatRoomLovershipOption == "CanOfferBeginWedding") && (RequestType == "Propose")) CharacterChangeMoney(Player, -100);
|
|
if ((ChatRoomLovershipOption == "CanBeginWedding") && (RequestType == "Accept")) CharacterChangeMoney(Player, -100);
|
|
ChatRoomLovershipOption = "";
|
|
ServerSend("AccountLovership", { MemberNumber: CurrentCharacter.MemberNumber, Action: RequestType });
|
|
if (RequestType == "Accept") DialogLeave();
|
|
}
|
|
|
|
/**
|
|
* Triggered when the player picks a drink from a character's maid tray.
|
|
* @param {string} DrinkType - Drink chosen.
|
|
* @param {number} Money - Cost of the drink.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomDrinkPick(DrinkType, Money) {
|
|
if (ChatRoomCanTakeDrink()) {
|
|
var Dictionary = [];
|
|
Dictionary.push({ Tag: "SourceCharacter", Text: Player.Name, MemberNumber: Player.MemberNumber });
|
|
Dictionary.push({ Tag: "DestinationCharacter", Text: CurrentCharacter.Name, MemberNumber: CurrentCharacter.MemberNumber });
|
|
Dictionary.push({ Tag: "TargetCharacter", Text: CurrentCharacter.Name, MemberNumber: CurrentCharacter.MemberNumber });
|
|
ServerSend("ChatRoomChat", { Content: "MaidDrinkPick" + DrinkType, Type: "Action", Dictionary: Dictionary });
|
|
ServerSend("ChatRoomChat", { Content: "MaidDrinkPick" + Money.toString(), Type: "Hidden", Target: CurrentCharacter.MemberNumber });
|
|
CharacterChangeMoney(Player, Money * -1);
|
|
DialogLeave();
|
|
}
|
|
}
|
|
|
|
function ChatRoomSendLoverRule(RuleType, Option) { ChatRoomSendRule(RuleType, Option, "Lover"); }
|
|
function ChatRoomSendOwnerRule(RuleType, Option) { ChatRoomSendRule(RuleType, Option, "Owner"); }
|
|
/**
|
|
* Sends a rule / restriction / punishment to the player's slave/lover client, it will be handled on the slave/lover's
|
|
* side when received.
|
|
* @param {string} RuleType - The rule selected.
|
|
* @param {"Quest" | "Leave"} Option - If the rule is a quest or we should just leave the dialog.
|
|
* @param {"Owner" | "Lover"} Sender - Type of the sender
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomSendRule(RuleType, Option, Sender) {
|
|
ServerSend("ChatRoomChat", { Content: Sender + "Rule" + RuleType, Type: "Hidden", Target: CurrentCharacter.MemberNumber });
|
|
if (Option == "Quest") {
|
|
if (ChatRoomQuestGiven.indexOf(CurrentCharacter.MemberNumber) >= 0) ChatRoomQuestGiven.splice(ChatRoomQuestGiven.indexOf(CurrentCharacter.MemberNumber), 1);
|
|
ChatRoomQuestGiven.push(CurrentCharacter.MemberNumber);
|
|
}
|
|
if ((Option == "Leave") || (Option == "Quest")) DialogLeave();
|
|
}
|
|
|
|
function ChatRoomGetLoverRule(RuleType) { return ChatRoomGetRule(RuleType, "Lover"); }
|
|
function ChatRoomGetOwnerRule(RuleType) { return ChatRoomGetRule(RuleType, "Owner"); }
|
|
|
|
/**
|
|
* Gets a rule from the current character
|
|
* @param {string} RuleType - The name of the rule to retrieve.
|
|
* @param {"Owner" | "Lover"} Sender - Type of the sender
|
|
* @returns {boolean} - The owner or lover rule corresponding to the requested rule name
|
|
*/
|
|
function ChatRoomGetRule(RuleType, Sender) {
|
|
return LogQueryRemote(CurrentCharacter, RuleType, Sender + "Rule");
|
|
}
|
|
|
|
|
|
/**
|
|
* Processes a rule sent to the player from her owner or from her lover.
|
|
* @param {object} data - Received rule data object.
|
|
* @returns {object} - Returns the data object, used to continue processing the chat message.
|
|
*/
|
|
function ChatRoomSetRule(data) {
|
|
|
|
// Only works if the sender is the player, and the player is fully collared
|
|
if ((data != null) && (Player.Ownership != null) && (Player.Ownership.Stage == 1) && (Player.Ownership.MemberNumber == data.Sender)) {
|
|
|
|
// Wardrobe/changing rules
|
|
if (data.Content == "OwnerRuleChangeAllow") LogDelete("BlockChange", "OwnerRule");
|
|
if (data.Content == "OwnerRuleChangeBlock1Hour") LogAdd("BlockChange", "OwnerRule", CurrentTime + 3600000);
|
|
if (data.Content == "OwnerRuleChangeBlock1Day") LogAdd("BlockChange", "OwnerRule", CurrentTime + 86400000);
|
|
if (data.Content == "OwnerRuleChangeBlock1Week") LogAdd("BlockChange", "OwnerRule", CurrentTime + 604800000);
|
|
if (data.Content == "OwnerRuleChangeBlock") LogAdd("BlockChange", "OwnerRule", CurrentTime + 1000000000000);
|
|
|
|
// Whisper rules
|
|
if (data.Content == "OwnerRuleWhisperAllow") LogDelete("BlockWhisper", "OwnerRule");
|
|
if (data.Content == "OwnerRuleWhisperBlock") { LogAdd("BlockWhisper", "OwnerRule"); ChatRoomSetTarget(null); }
|
|
|
|
// Key rules
|
|
if (data.Content == "OwnerRuleKeyAllow") LogDelete("BlockKey", "OwnerRule");
|
|
if (data.Content == "OwnerRuleKeyConfiscate") {InventoryConfiscateKey(); DialogLentLockpicks = false;}
|
|
if (data.Content == "OwnerRuleKeyBlock") LogAdd("BlockKey", "OwnerRule");
|
|
if (data.Content == "OwnerRuleSelfOwnerLockAllow") LogDelete("BlockOwnerLockSelf", "OwnerRule");
|
|
if (data.Content == "OwnerRuleSelfOwnerLockBlock") LogAdd("BlockOwnerLockSelf", "OwnerRule");
|
|
|
|
// Remote rules
|
|
if (data.Content == "OwnerRuleRemoteAllow") LogDelete("BlockRemote", "OwnerRule");
|
|
if (data.Content == "OwnerRuleRemoteAllowSelf") LogDelete("BlockRemoteSelf", "OwnerRule");
|
|
if (data.Content == "OwnerRuleRemoteConfiscate") InventoryConfiscateRemote();
|
|
if (data.Content == "OwnerRuleRemoteBlock") LogAdd("BlockRemote", "OwnerRule");
|
|
if (data.Content == "OwnerRuleRemoteBlockSelf") LogAdd("BlockRemoteSelf", "OwnerRule");
|
|
|
|
// Timer cell punishment
|
|
var TimerCell = 0;
|
|
if (data.Content == "OwnerRuleTimerCell5") TimerCell = 5;
|
|
if (data.Content == "OwnerRuleTimerCell15") TimerCell = 15;
|
|
if (data.Content == "OwnerRuleTimerCell30") TimerCell = 30;
|
|
if (data.Content == "OwnerRuleTimerCell60") TimerCell = 60;
|
|
if (TimerCell > 0) {
|
|
ServerSend("ChatRoomChat", { Content: "ActionGrabbedForCell", Type: "Action", Dictionary: [{ Tag: "TargetCharacterName", Text: Player.Name, MemberNumber: Player.MemberNumber }] });
|
|
|
|
DialogLentLockpicks = false;
|
|
ChatRoomClearAllElements();
|
|
ServerSend("ChatRoomLeave", "");
|
|
CharacterDeleteAllOnline();
|
|
CellLock(TimerCell);
|
|
}
|
|
|
|
// Collar Rules
|
|
if (data.Content == "OwnerRuleCollarRelease") {
|
|
if ((InventoryGet(Player, "ItemNeck") != null) && (InventoryGet(Player, "ItemNeck").Asset.Name == "SlaveCollar")) {
|
|
InventoryRemove(Player, "ItemNeck");
|
|
ChatRoomCharacterItemUpdate(Player, "ItemNeck");
|
|
ServerSend("ChatRoomChat", { Content: "PlayerOwnerCollarRelease", Type: "Action", Dictionary: [{Tag: "DestinationCharacterName", Text: Player.Name, MemberNumber: Player.MemberNumber}] });
|
|
}
|
|
LogAdd("ReleasedCollar", "OwnerRule");
|
|
}
|
|
if (data.Content == "OwnerRuleCollarWear") {
|
|
if ((InventoryGet(Player, "ItemNeck") == null) || ((InventoryGet(Player, "ItemNeck") != null) && (InventoryGet(Player, "ItemNeck").Asset.Name != "SlaveCollar"))) {
|
|
ServerSend("ChatRoomChat", { Content: "PlayerOwnerCollarWear", Type: "Action", Dictionary: [{Tag: "TargetCharacterName", Text: Player.Name, MemberNumber: Player.MemberNumber}] });
|
|
}
|
|
LogDelete("ReleasedCollar", "OwnerRule");
|
|
LoginValidCollar();
|
|
}
|
|
|
|
// Forced labor
|
|
if (data.Content == "OwnerRuleLaborMaidDrinks" && Player.CanTalk()) {
|
|
CharacterSetActivePose(Player, null);
|
|
var D = TextGet("ActionGrabbedToServeDrinksIntro");
|
|
ServerSend("ChatRoomChat", { Content: "ActionGrabbedToServeDrinks", Type: "Action", Dictionary: [{ Tag: "TargetCharacterName", Text: Player.Name, MemberNumber: Player.MemberNumber }] });
|
|
DialogLentLockpicks = false;
|
|
ChatRoomClearAllElements();
|
|
ServerSend("ChatRoomLeave", "");
|
|
CharacterDeleteAllOnline();
|
|
CommonSetScreen("Room", "MaidQuarters");
|
|
CharacterSetCurrent(MaidQuartersMaid);
|
|
MaidQuartersMaid.CurrentDialog = D;
|
|
MaidQuartersMaid.Stage = "205";
|
|
MaidQuartersOnlineDrinkFromOwner = true;
|
|
}
|
|
|
|
// Switches it to a server message to announce the new rule to the player
|
|
data.Type = "ServerMessage";
|
|
|
|
ChatRoomGetLoadRules(data.Sender);
|
|
}
|
|
|
|
// Only works if the sender is the lover of the player
|
|
if ((data != null) && Player.GetLoversNumbers().includes(data.Sender)) {
|
|
if (data.Content == "LoverRuleSelfLoverLockAllow") LogDelete("BlockLoverLockSelf", "LoverRule");
|
|
if (data.Content == "LoverRuleSelfLoverLockBlock") LogAdd("BlockLoverLockSelf", "LoverRule");
|
|
if (data.Content == "LoverRuleOwnerLoverLockAllow") LogDelete("BlockLoverLockOwner", "LoverRule");
|
|
if (data.Content == "LoverRuleOwnerLoverLockBlock") LogAdd("BlockLoverLockOwner", "LoverRule");
|
|
|
|
data.Type = "ServerMessage";
|
|
|
|
ChatRoomGetLoadRules(data.Sender);
|
|
}
|
|
|
|
// Returns the data packet
|
|
return data;
|
|
|
|
}
|
|
|
|
/**
|
|
* Sends quest money to the player's owner.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomGiveMoneyForOwner() {
|
|
if (ChatRoomCanGiveMoneyForOwner()) {
|
|
ServerSend("ChatRoomChat", { Content: "ActionGiveEnvelopeToOwner", Type: "Action", Dictionary: [{ Tag: "TargetCharacterName", Text: Player.Name, MemberNumber: Player.MemberNumber }] });
|
|
ServerSend("ChatRoomChat", { Content: "PayQuest" + ChatRoomMoneyForOwner.toString(), Type: "Hidden", Target: CurrentCharacter.MemberNumber });
|
|
ChatRoomMoneyForOwner = 0;
|
|
DialogLeave();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles the reception of quest data, when payment is received.
|
|
* @param {object} data - Data object containing the payment.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomPayQuest(data) {
|
|
if ((data != null) && (data.Sender != null) && (ChatRoomQuestGiven.indexOf(data.Sender) >= 0)) {
|
|
var M = parseInt(data.Content.substring(8));
|
|
if ((M == null) || isNaN(M)) M = 0;
|
|
if (M < 0) M = 0;
|
|
if (M > 30) M = 30;
|
|
CharacterChangeMoney(Player, M);
|
|
ChatRoomQuestGiven.splice(ChatRoomQuestGiven.indexOf(data.Sender), 1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggered when online game data comes in
|
|
* @param {object} data - Game data to process, sent to the current game handler.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomOnlineBountyHandleData(data) {
|
|
if (data.finishTime && data.target == Player.MemberNumber) {
|
|
ChatRoomMessage({ Content: "OnlineBountySuitcaseOngoing", Type: "Action", Dictionary: [{Tag: "TIMEREMAINING", Text: ""+Math.max(1, Math.ceil((data.finishTime - CommonTime())/60000))}], Sender: Player.MemberNumber });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggered when a game message comes in, we forward it to the current online game being played.
|
|
* @param {object} data - Game data to process, sent to the current game handler.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomGameResponse(data) {
|
|
if (data.Data.KinkyDungeon)
|
|
KinkyDungeonHandleData(data.Data.KinkyDungeon, data.Sender);
|
|
else if (KidnapLeagueOnlineBountyTarget && data.Data.OnlineBounty && !Player.GhostList.includes(data.sender))
|
|
ChatRoomOnlineBountyHandleData(data.Data.OnlineBounty);
|
|
else if (ChatRoomGame == "LARP") GameLARPProcess(data);
|
|
}
|
|
|
|
/**
|
|
* Triggered when the player uses the /safeword command, we revert the character if safewords are enabled, and display
|
|
* a warning in chat if not.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomSafewordChatCommand() {
|
|
if (DialogChatRoomCanSafeword())
|
|
ChatRoomSafewordRevert();
|
|
else if (CurrentScreen == "ChatRoom") {
|
|
var msg = {Sender: Player.MemberNumber, Content: "SafewordDisabled", Type: "Action"};
|
|
ChatRoomMessage(msg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggered when the player activates her safeword to revert, we swap her appearance to the state when she entered the
|
|
* chat room lobby, minimum permission becomes whitelist and up.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomSafewordRevert() {
|
|
if (ChatSearchSafewordAppearance != null) {
|
|
Player.Appearance = ChatSearchSafewordAppearance.slice(0);
|
|
Player.ActivePose = ChatSearchSafewordPose;
|
|
CharacterRefresh(Player);
|
|
ChatRoomCharacterUpdate(Player);
|
|
ServerSend("ChatRoomChat", { Content: "ActionActivateSafewordRevert", Type: "Action", Dictionary: [{ Tag: "SourceCharacter", Text: Player.Name }] });
|
|
if (Player.ItemPermission < 3) {
|
|
Player.ItemPermission = 3;
|
|
ServerAccountUpdate.QueueData({ ItemPermission: Player.ItemPermission }, true);
|
|
setTimeout(() => ChatRoomCharacterUpdate(Player), 5000);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triggered when the player activates her safeword and wants to be released, we remove all bondage from her and return
|
|
* her to the chat search screen.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomSafewordRelease() {
|
|
CharacterReleaseTotal(Player);
|
|
CharacterRefresh(Player);
|
|
ServerSend("ChatRoomChat", { Content: "ActionActivateSafewordRelease", Type: "Action", Dictionary: [{Tag: "SourceCharacter", Text: Player.Name}] });
|
|
|
|
DialogLentLockpicks = false;
|
|
ChatRoomClearAllElements();
|
|
ServerSend("ChatRoomLeave", "");
|
|
CommonSetScreen("Online","ChatSearch");
|
|
}
|
|
|
|
/**
|
|
* Concatenates the list of users to ban.
|
|
* @param {boolean} IncludesBlackList - Adds the blacklist to the banlist
|
|
* @param {boolean} IncludesGhostList - Adds the ghostlist to the banlist
|
|
* @param {number[]} [ExistingList] - The existing Banlist, if applicable
|
|
* @returns {number[]} Complete array of members to ban
|
|
*/
|
|
function ChatRoomConcatenateBanList(IncludesBlackList, IncludesGhostList, ExistingList) {
|
|
var BanList = Array.isArray(ExistingList) ? ExistingList : [];
|
|
if (IncludesBlackList) BanList = BanList.concat(Player.BlackList);
|
|
if (IncludesGhostList) BanList = BanList.concat(Player.GhostList);
|
|
return BanList.filter((MemberNumber, Idx, Arr) => Arr.indexOf(MemberNumber) == Idx);
|
|
}
|
|
|
|
/**
|
|
* Concatenates the list of users for Admin list.
|
|
* @param {boolean} IncludesOwner - Adds the owner to the admin list
|
|
* @param {boolean} IncludesLovers - Adds lovers to the admin list
|
|
* @param {number[]} [ExistingList] - The existing Admin list, if applicable
|
|
* @returns {number[]} Complete array of admin members
|
|
*/
|
|
function ChatRoomConcatenateAdminList(IncludesOwner, IncludesLovers, ExistingList) {
|
|
var AdminList = Array.isArray(ExistingList) ? ExistingList : [];
|
|
if (IncludesOwner) AdminList = AdminList.concat(Player.Ownership.MemberNumber);
|
|
if (IncludesLovers) CommonArrayConcatDedupe(AdminList, Player.GetLoversNumbers(true));
|
|
return AdminList.filter((MemberNumber, Idx, Arr) => Arr.indexOf(MemberNumber) == Idx);
|
|
}
|
|
|
|
/**
|
|
* Handles a request from another player to read the player's log entries that they are permitted to read. Lovers and
|
|
* owners can read certain entries from the player's log.
|
|
* @param {Character|number} C - A character object representing the requester, or the account number of the requester.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomGetLoadRules(C) {
|
|
if (typeof C === "number") {
|
|
C = ChatRoomCharacter.find(CC => CC.MemberNumber == C);
|
|
}
|
|
if (C == null) return;
|
|
if (Player.Ownership && Player.Ownership.MemberNumber != null && Player.Ownership.MemberNumber == C.MemberNumber) {
|
|
ServerSend("ChatRoomChat", {
|
|
Content: "RuleInfoSet",
|
|
Type: "Hidden",
|
|
Target: C.MemberNumber,
|
|
Dictionary: LogGetOwnerReadableRules(C.IsLoverOfPlayer()),
|
|
});
|
|
} else if (C.IsLoverOfPlayer()) {
|
|
ServerSend("ChatRoomChat", {
|
|
Content: "RuleInfoSet",
|
|
Type: "Hidden",
|
|
Target: C.MemberNumber,
|
|
Dictionary: LogGetLoverReadableRules(),
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles a response from another player containing the rules that the current player is allowed to read.
|
|
* @param {Character} C - Character to set the rules on
|
|
* @param {LogRecord[]} Rule - An array of rules that the current player can read.
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomSetLoadRules(C, Rule) {
|
|
if (Array.isArray(Rule)) C.Rule = Rule;
|
|
}
|
|
|
|
/**
|
|
* Take a screenshot of all characters in the chatroom
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomPhotoFullRoom() {
|
|
// Get the room dimensions
|
|
let Space = ChatRoomCharacterCount >= 2 ? 1000 / Math.min(ChatRoomCharacterCount, 5) : 500;
|
|
let Zoom = ChatRoomCharacterCount >= 3 && ChatRoomCharacterCount < 6 ? Space / 400 : 1;
|
|
let Y = ChatRoomCharacterCount <= 5 ? 1000 * (1 - Zoom) / 2 : 0;
|
|
let Width = ChatRoomCharacterCount === 1 ? 500 : 1000;
|
|
|
|
// Take the photo
|
|
ChatRoomPhoto(0, Y, Width, 1000 * Zoom, ChatRoomCharacter);
|
|
}
|
|
|
|
/**
|
|
* Take a screenshot of the player and current character
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomPhotoCurrentCharacters() {
|
|
ChatRoomPhoto(0, 0, 1000, 1000, [Player, CurrentCharacter]);
|
|
}
|
|
|
|
/**
|
|
* Take a screenshot of the player
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function DialogChatRoomPhotoPlayer() {
|
|
ChatRoomPhoto(500, 0, 500, 1000, [Player]);
|
|
}
|
|
|
|
/**
|
|
* Take a screenshot in a chatroom, temporary removing emoticons
|
|
* @param {number} Left - Position of the area to capture from the left of the canvas
|
|
* @param {number} Top - Position of the area to capture from the top of the canvas
|
|
* @param {number} Width - Width of the area to capture
|
|
* @param {number} Height - Height of the area to capture
|
|
* @param {any} Characters - The characters that will be included in the screenshot
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomPhoto(Left, Top, Width, Height, Characters) {
|
|
// Temporarily remove AFK emoticons
|
|
let CharsToReset = [];
|
|
for (let CR = 0; CR < Characters.length; CR++) {
|
|
let C = Characters[CR];
|
|
let Emoticon = C.Appearance.find(A => A.Asset.Group.Name == "Emoticon");
|
|
if (Emoticon && Emoticon.Property && Emoticon.Property.Expression == "Afk") {
|
|
CharsToReset.push(C);
|
|
Emoticon.Property.Expression = null;
|
|
CharacterRefresh(C, false);
|
|
}
|
|
}
|
|
|
|
// Take the photo
|
|
CommonTakePhoto(Left, Top, Width, Height);
|
|
|
|
// Revert temporary changes
|
|
for (let CR = 0; CR < CharsToReset.length; CR++) {
|
|
let C = CharsToReset[CR];
|
|
C.Appearance.find(A => A.Asset.Group.Name == "Emoticon").Property.Expression = "Afk";
|
|
CharacterRefresh(C, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns whether the most recent chat message is on screen
|
|
* @returns {boolean} - TRUE if the screen has focus and the chat log is scrolled to the bottom
|
|
*/
|
|
function ChatRoomNotificationNewMessageVisible() {
|
|
return document.hasFocus() && ElementIsScrolledToEnd("TextAreaChatLog");
|
|
}
|
|
|
|
/**
|
|
* Raise a notification for the new chat message if required
|
|
* @param {Character} C - The character that sent the message
|
|
* @param {string} msg - The text of the message
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomNotificationRaiseChatMessage(C, msg) {
|
|
if (C.ID !== 0
|
|
&& Player.NotificationSettings.ChatMessage.AlertType !== NotificationAlertType.NONE
|
|
&& !ChatRoomNotificationNewMessageVisible())
|
|
{
|
|
NotificationRaise(NotificationEventType.CHATMESSAGE, { body: msg, character: C, useCharAsIcon: true });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets any previously raised Chat Message or Chatroom Join notifications if required
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomNotificationReset() {
|
|
if (CurrentScreen !== "ChatRoom" || ChatRoomNotificationNewMessageVisible()) {
|
|
NotificationReset(NotificationEventType.CHATMESSAGE);
|
|
}
|
|
if (document.hasFocus()) NotificationReset(NotificationEventType.CHATJOIN);
|
|
}
|
|
|
|
/**
|
|
* Returns whether a notification should be raised for the character entering a chatroom
|
|
* @param {Character} C - The character that entered the room
|
|
* @returns {boolean} - Whether a notification should be raised
|
|
*/
|
|
function ChatRoomNotificationRaiseChatJoin(C) {
|
|
let raise = false;
|
|
if (!document.hasFocus()) {
|
|
const settings = Player.NotificationSettings.ChatJoin;
|
|
if (settings.AlertType === NotificationAlertType.NONE) raise = false;
|
|
else if (!settings.Owner && !settings.Lovers && !settings.Friendlist && !settings.Subs) raise = true;
|
|
else if (settings.Owner && Player.IsOwnedByMemberNumber(C.MemberNumber)) raise = true;
|
|
else if (settings.Lovers && C.IsLoverOfPlayer()) raise = true;
|
|
else if (settings.Friendlist && Player.FriendList.includes(C.MemberNumber)) raise = true;
|
|
else if (settings.Subs && C.IsOwnedByPlayer()) raise = true;
|
|
}
|
|
return raise;
|
|
}
|
|
|
|
/**
|
|
* Updates the chatroom with the player's stored chatroom data if needed (happens when entering a recreated chatroom for
|
|
* the first time)
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomRecreate() {
|
|
if (CurrentTime > ChatRoomNewRoomToUpdateTimer && ChatRoomNewRoomToUpdate && Player.ImmersionSettings && Player.ImmersionSettings.ReturnToChatRoomAdmin &&
|
|
Player.ImmersionSettings.ReturnToChatRoom && Player.LastChatRoomAdmin) {
|
|
// Add the player if they are not an admin
|
|
if (!Player.LastChatRoomAdmin.includes(Player.MemberNumber) && Player.LastChatRoomPrivate) {
|
|
Player.LastChatRoomAdmin.push(Player.MemberNumber);
|
|
}
|
|
var UpdatedRoom = {
|
|
Name: Player.LastChatRoom,
|
|
Description: Player.LastChatRoomDesc,
|
|
Background: Player.LastChatRoomBG,
|
|
Limit: "" + Player.LastChatRoomSize,
|
|
Admin: Player.LastChatRoomAdmin,
|
|
Ban: Player.LastChatRoomBan,
|
|
BlockCategory: ChatRoomData.BlockCategory,
|
|
Game: ChatRoomData.Game,
|
|
Private: Player.LastChatRoomPrivate,
|
|
Locked: ChatRoomData.Locked,
|
|
};
|
|
|
|
ServerSend("ChatRoomAdmin", { MemberNumber: Player.ID, Room: UpdatedRoom, Action: "Update" });
|
|
ChatRoomNewRoomToUpdate = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether or not the player's last chatroom data needs updating
|
|
* @returns {void} - Nothing
|
|
*/
|
|
function ChatRoomCheckForLastChatRoomUpdates() {
|
|
const Blacklist = Player.BlackList || [];
|
|
// Check whether the chatroom contains at least one "safe" character (a friend, owner, or non-blacklisted player)
|
|
const ContainsSafeCharacters = ChatRoomCharacter.length === 1 || ChatRoomCharacter.some((Char) => {
|
|
return Char.ID !== 0 && (
|
|
Player.FriendList.includes(Char.MemberNumber) ||
|
|
Player.IsOwnedByMemberNumber(Char.MemberNumber) ||
|
|
!Blacklist.includes(Char.MemberNumber)
|
|
);
|
|
});
|
|
|
|
if (!ChatRoomData || !ContainsSafeCharacters) {
|
|
// If the room only contains blacklisted characters, do not save the room data
|
|
ChatRoomSetLastChatRoom("");
|
|
} else if (Player.ImmersionSettings && ChatRoomDataChanged()) {
|
|
// Otherwise save the chatroom data if it has changed
|
|
ChatRoomSetLastChatRoom(ChatRoomData.Name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines whether or not the current chatroom data differs from the locally stroed chatroom data
|
|
* @returns {boolean} - TRUE if the stored chatroom data is different from the current chatroom data, FALSE otherwise
|
|
*/
|
|
function ChatRoomDataChanged() {
|
|
return ChatRoomLastName != ChatRoomData.Name ||
|
|
ChatRoomLastBG != ChatRoomData.Background ||
|
|
ChatRoomLastSize != ChatRoomData.Limit ||
|
|
ChatRoomLastPrivate != ChatRoomData.Private ||
|
|
ChatRoomLastDesc != ChatRoomData.Description ||
|
|
!CommonArraysEqual(ChatRoomLastAdmin, ChatRoomData.Admin) ||
|
|
!CommonArraysEqual(ChatRoomLastBan, ChatRoomData.Ban);
|
|
}
|
|
|
|
function ChatRoomRefreshFontSize() {
|
|
ChatRoomFontSize = ChatRoomFontSizes[Player.ChatSettings.FontSize || "Medium"];
|
|
}
|
|
|
|
/**
|
|
* Checks if the message can be sent as chat or the player should be warned
|
|
* @param {string} Message - User input
|
|
* @param {Character} WhisperTarget
|
|
* @returns {boolean}
|
|
*/
|
|
function ChatRoomShouldBlockGaggedOOCMessage(Message, WhisperTarget) {
|
|
if (ChatRoomTargetMemberNumber == null && !Message.includes("(")) return false;
|
|
if (Player.ImmersionSettings == null || !Player.ImmersionSettings.BlockGaggedOOC) return false;
|
|
if (Player.CanTalk()) return false;
|
|
|
|
if (WhisperTarget && WhisperTarget.Effect.includes("VRAvatars"))
|
|
if (Player.Effect.includes("HideRestraints") && Player.Effect.includes("VRAvatars"))
|
|
return false;
|
|
|
|
return true;
|
|
}
|