// Main variables var CurrentIntro; var CurrentStage; var CurrentText; var CurrentChapter; var CurrentScreen; var CurrentLanguageTag = "EN"; var OverridenIntroText; var OverridenIntroImage; var LeaveChapter = ""; var LeaveScreen = ""; var LeaveIcon = ""; var MouseX = 0; var MouseY = 0; var KeyPress = ""; var IsMobile = false; var TextPhase = 0; var CSVCache = {}; var MaxFightSequence = 500; var MaxRaceSequence = 1000; var AllowCheats = false; // Array variables var IntroStage = 0; var IntroLoveReq = 1; var IntroSubReq = 2; var IntroVarReq = 3; var IntroText = 4; var IntroImage = 5; var StageNumber = 0; var StageLoveReq = 1; var StageSubReq = 2; var StageVarReq = 3; var StageInteractionText = 4; var StageInteractionResult = 5; var StageNextStage = 6; var StageLoveMod = 7; var StageSubMod = 8; var StageFunction = 9; var TextTag = 0; var TextContent = 1; var FightMoveType = 0; var FightMoveTime = 1; var RaceMoveType = 0; var RaceMoveTime = 1; // Common variables var Common_BondageAllowed = true; var Common_SelfBondageAllowed = true; var Common_PlayerName = ""; var Common_PlayerOwner = ""; var Common_PlayerLover = ""; var Common_PlayerRestrained = false; var Common_PlayerGagged = false; var Common_PlayerBlinded = false; var Common_PlayerChaste = false; var Common_PlayerNotRestrained = true; var Common_PlayerNotGagged = true; var Common_PlayerNotBlinded = true; var Common_PlayerClothed = true; var Common_PlayerUnderwear = false; var Common_PlayerNaked = false; var Common_PlayerCloth = ""; var Common_PlayerCostume = ""; var Common_PlayerPose = ""; var Common_ActorIsLover = false; var Common_ActorIsOwner = false; var Common_ActorIsOwned = false; var Common_Number = ""; // Returns TRUE if the variable is a number function IsNumeric(n) { return !isNaN(parseFloat(n)) && isFinite(n); } // Returns the current date and time in a yyyy-mm-dd hh:mm:ss format function GetFormatDate() { var d = new Date(); var yyyy = d.getFullYear(); var mm = d.getMonth() < 9 ? "0" + (d.getMonth() + 1) : (d.getMonth() + 1); // getMonth() is zero-based var dd = d.getDate() < 10 ? "0" + d.getDate() : d.getDate(); var hh = d.getHours() < 10 ? "0" + d.getHours() : d.getHours(); var min = d.getMinutes() < 10 ? "0" + d.getMinutes() : d.getMinutes(); var ss = d.getSeconds() < 10 ? "0" + d.getSeconds() : d.getSeconds(); return "".concat(yyyy).concat("-").concat(mm).concat("-").concat(dd).concat(" ").concat(hh).concat(":").concat(min).concat(":").concat(ss); } // Used to detect whether the users browser is an mobile browser function DetectMobile() { // First check if (sessionStorage.desktop) return false; else if (localStorage.mobile) return true; // Alternative check var mobile = ['iphone','ipad','android','blackberry','nokia','opera mini','windows mobile','windows phone','iemobile','mobile/']; for (var i in mobile) if (navigator.userAgent.toLowerCase().indexOf(mobile[i].toLowerCase()) > 0) return true; // If nothing is found, we assume desktop return false; } // Parse a CSV file function ParseCSV(str) { var arr = []; var quote = false; // true means we're inside a quoted field // iterate over each character, keep track of current row and column (of the returned array) for (var row = col = c = 0; c < str.length; c++) { var cc = str[c], nc = str[c+1]; // current character, next character arr[row] = arr[row] || []; // create a new row if necessary arr[row][col] = arr[row][col] || ''; // create a new column (start with empty string) if necessary // If the current character is a quotation mark, and we're inside a // quoted field, and the next character is also a quotation mark, // add a quotation mark to the current column and skip the next character if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; } // If it's just one quotation mark, begin/end quoted field if (cc == '"') { quote = !quote; continue; } // If it's a comma and we're not in a quoted field, move on to the next column if (cc == ',' && !quote) { ++col; continue; } // If it's a newline and we're not in a quoted field, move on to the next // row and move to column 0 of that new row if (cc == '\n' && !quote) { ++row; col = 0; continue; } // Otherwise, append the current character to the current column arr[row][col] += cc; } return arr; } // Read a CSV file from the web site function ReadCSV(Array, ChapterOrPath, Screen, Type, Language) { // Changed from a single path to various arguments and internally concatenate them // This ternary operator is used to keep backward compatibility var Path = (Screen && Type) ? ChapterOrPath + "/" + Screen + "/" + Type + (Language ? "_" : "") + (Language || "") + ".csv" : ChapterOrPath; if (CSVCache[Path]) { window[Array] = CSVCache[Path]; return; } // Opens the file, parse it and returns the result in an array Get(Path + (TranslationCheatAllow ? "?force_" + TranslationCacheCounter : ""), function() { if (this.status == 200) { CSVCache[Path] = ParseCSV(this.responseText); window[Array] = CSVCache[Path]; } else if (this.status == 404 && Language && Language != "EN") { // If language isn't EN and the file doesn't exist, then fallback to EN ReadCSV(Array, ChapterOrPath, Screen, Type, "EN"); } }); } // AJAX utility function Get(Path, Callback) { var xhr = new XMLHttpRequest(); xhr.open("GET", Path); xhr.onreadystatechange = function() { if (this.readyState == 4) Callback.bind(this)(); }; xhr.send(null); } // Shuffles all array elements at random function ArrayShuffle(a) { var j, x, i; for (i = a.length - 1; i > 0; i--) { j = Math.floor(Math.random() * (i + 1)); x = a[i]; a[i] = a[j]; a[j] = x; } return a; } // Returns a working language if translation isn't fully ready function GetWorkingLanguage() { return GetWorkingLanguageForChapter(CurrentChapter); } // Returns a working language for a specific chapter function GetWorkingLanguageForChapter(Chapter) { if ((CurrentLanguageTag == "FR") && ["C000_Intro", "C001_BeforeClass", "C002_FirstClass", "C003_MorningDetention", "C004_ArtClass", "C005_GymClass", "C006_Isolation", "C007_LunchBreak", "C008_DramaClass", "C009_Library", "C999_Common"].indexOf(Chapter) >= 0) return "FR"; if ((CurrentLanguageTag == "DE") && ["C000_Intro", "C001_BeforeClass", "C002_FirstClass", "C003_MorningDetention", "C004_ArtClass", "C005_GymClass", "C006_Isolation", "C007_LunchBreak", "C008_DramaClass", "C009_Library", "C010_Revenge", "C011_LiteratureClass", "C012_AfterClass", "C013_BondageClub", "C101_KinbakuClub", "C999_Common"].indexOf(Chapter) >= 0) return "DE"; if ((CurrentLanguageTag == "PL") && ["C000_Intro"].indexOf(Chapter) >= 0) return "PL"; if ((CurrentLanguageTag == "ES") && ["C000_Intro", "C001_BeforeClass", "C002_FirstClass", "C003_MorningDetention"].indexOf(Chapter) >= 0) return "ES"; if ((CurrentLanguageTag == "CN") && ["C000_Intro", "C001_BeforeClass", "C002_FirstClass", "C003_MorningDetention", "C004_ArtClass", "C005_GymClass", "C006_Isolation", "C007_LunchBreak", "C008_DramaClass", "C009_Library", "C010_Revenge", "C011_LiteratureClass", "C012_AfterClass", "C013_BondageClub", "C101_KinbakuClub", "C999_Common"].indexOf(Chapter) >= 0) return "CN"; if ((CurrentLanguageTag == "RU") && ["C000_Intro", "C001_BeforeClass"].indexOf(Chapter) >= 0) return "RU"; if ((CurrentLanguageTag == "CS") && ["C000_Intro", "C001_BeforeClass", "C002_FirstClass", "C003_MorningDetention", "C004_ArtClass", "C005_GymClass", "C006_Isolation", "C007_LunchBreak", "C999_Common"].indexOf(Chapter) >= 0) return "CS"; return "EN"; //return CurrentLanguageTag; } // Load the interactions from a scene and keep it in common variable function LoadInteractions() { ReadCSV("CurrentIntro", CurrentChapter, CurrentScreen, "Intro", GetWorkingLanguage()); ReadCSV("CurrentStage", CurrentChapter, CurrentScreen, "Stage", GetWorkingLanguage()); LoadText(); } // Load the custom texts from a scene and keep it in common variable function LoadText() { ReadCSV("CurrentText", CurrentChapter, CurrentScreen, "Text", GetWorkingLanguage()); } // Calls a dynamic function (if it exists) function DynamicFunction(FunctionName) { if (typeof window[FunctionName.substr(0, FunctionName.indexOf("("))] == "function") { var Fct = new Function(FunctionName); Fct(); } else console.log("Trying to launch invalid function: " + FunctionName); } // Set the current scene (chapter and screen) function SetScene(Chapter, Screen) { // Keep the chapter and screen CurrentStage = null; CurrentIntro = null; CurrentText = null; CurrentActor = ""; CurrentChapter = Chapter; CurrentScreen = Screen; OverridenIntroText = ""; OverridenIntroImage = ""; LeaveIcon = ""; LeaveScreen = ""; LeaveChapter = Chapter; Common_ActorIsLover = false; Common_ActorIsOwner = false; Common_ActorIsOwned = false; // Load the screen code DynamicFunction(CurrentChapter + "_" + CurrentScreen + "_Load()"); } // Validates if any interaction was clicked function ClickInteraction(CurrentStagePosition) { // Make sure the current stage is loaded if (CurrentStage != null) { // If a regular option was clicked, we process it var Pos = 0; for (var L = 0; L < CurrentStage.length; L++) if (CurrentStage[L][StageNumber] == CurrentStagePosition) if (ActorInteractionAvailable(CurrentStage[L][StageLoveReq], CurrentStage[L][StageSubReq], CurrentStage[L][StageVarReq], CurrentStage[L][StageInteractionText], false)) { if ((MouseX >= (Pos % 2) * 300) && (MouseX <= ((Pos % 2) * 300) + 299) && (MouseY >= 151 + (Math.round((Pos - 1) / 2) * 90)) && (MouseY <= 240 + (Math.round((Pos - 1) / 2) * 90))) { window[CurrentChapter + "_" + CurrentScreen + "_CurrentStage"] = CurrentStage[L][StageNextStage]; OverridenIntroText = CurrentStage[L][StageInteractionResult]; ActorChangeAttitude(CurrentStage[L][StageLoveMod], CurrentStage[L][StageSubMod]); // Check if the interaction has a time tag var MinuteString = "ADD_MINUTES:"; var MinuteStringIndex = CurrentStage[L][StageInteractionText].indexOf(MinuteString); if (MinuteStringIndex >= 0) { var MinuteCount = parseInt(CurrentStage[L][StageInteractionText].substring(MinuteStringIndex + MinuteString.length)); // If the text after the tag isn't a valid number, output a log message and assume the default time if (isNaN(MinuteCount)) { console.log("Invalid minute expression in interaction: " + CurrentStage[L][StageInteractionText]); CurrentTime = CurrentTime + 10000; } else CurrentTime = CurrentTime + MinuteCount * 60000; } else CurrentTime = CurrentTime + 10000; if (CurrentStage[L][StageFunction].trim() != "") DynamicFunction(CurrentChapter + "_" + CurrentScreen + "_" + CurrentStage[L][StageFunction].trim()); return; } Pos = Pos + 1; } } } // Returns the text for the current scene associated with the tag function GetText(Tag) { // Make sure the text CSV file is loaded if (CurrentText != null) { // Cycle the text to find a matching tag and returns the text content Tag = Tag.trim().toUpperCase(); for (var T = 0; T < CurrentText.length; T++) if (CurrentText[T][TextTag].trim().toUpperCase() == Tag) return CurrentText[T][TextContent].trim(); // Returns an error message return "MISSING TEXT FOR TAG: " + Tag.trim(); } else return ""; } // Returns the text for a specific CSV associated with the tag function GetCSVText(CSVText, Tag) { // Make sure the text CSV file is loaded if (CSVText != null) { // Cycle the text to find a matching tag and returns the text content Tag = Tag.trim().toUpperCase(); for (var T = 0; T < CSVText.length; T++) if (CSVText[T][TextTag].trim().toUpperCase() == Tag) return CSVText[T][TextContent].trim(); // Returns an error message return "MISSING TEXT FOR TAG: " + Tag.trim(); } else return ""; } // Triggers the leave or wait button if needed function LeaveButtonClick() { // If the wait option was clicked, we skip 2 minutes if (LeaveIcon == "Wait") if ((MouseX >= 1125) && (MouseX <= 1200) && (MouseY >= 600) && (MouseY <= 675)) CurrentTime = CurrentTime + 120000; // If the leave option was clicked, we return to the previous screen if ((LeaveIcon == "Leave") && (LeaveScreen != "")) if ((MouseX >= 1125) && (MouseX <= 1200) && (MouseY >= 600) && (MouseY <= 675)) SetScene(LeaveChapter, LeaveScreen); } // Creates a path from the supplied paths parts function GetPath(paths) { var path = arguments[0]; for (var index = 1; index < arguments.length; index++) { path += "/" + arguments[index]; } return path; }