diff --git a/BondageClub/Backgrounds/LewdgamblerEntrance.jpg b/BondageClub/Backgrounds/LewdgamblerEntrance.jpg new file mode 100644 index 0000000000..53df510e1a Binary files /dev/null and b/BondageClub/Backgrounds/LewdgamblerEntrance.jpg differ diff --git a/BondageClub/Backgrounds/LewdgamblerRoom.jpg b/BondageClub/Backgrounds/LewdgamblerRoom.jpg new file mode 100644 index 0000000000..958af63056 Binary files /dev/null and b/BondageClub/Backgrounds/LewdgamblerRoom.jpg differ diff --git a/BondageClub/Backgrounds/LewdgamblerRoomGameTable.jpg b/BondageClub/Backgrounds/LewdgamblerRoomGameTable.jpg new file mode 100644 index 0000000000..9b53d9bca5 Binary files /dev/null and b/BondageClub/Backgrounds/LewdgamblerRoomGameTable.jpg differ diff --git a/BondageClub/Icons/Lewdgambler.png b/BondageClub/Icons/Lewdgambler.png new file mode 100644 index 0000000000..73ba282fba Binary files /dev/null and b/BondageClub/Icons/Lewdgambler.png differ diff --git a/BondageClub/Screens/Room/Gambling/Gambling.js b/BondageClub/Screens/Room/Gambling/Gambling.js index 8eed31e85a..7f578a899c 100644 --- a/BondageClub/Screens/Room/Gambling/Gambling.js +++ b/BondageClub/Screens/Room/Gambling/Gambling.js @@ -272,25 +272,25 @@ function GamblingSimpleDiceController(SimpleDiceState) { if (GamblingPlayerDice > GamblingNpcDice) { GamblingFirstSub.AllowItem = true; GamblingFirstSub.Stage = 81; - } + } if (GamblingPlayerDice < GamblingNpcDice) { GamblingFirstSub.AllowItem = false; GamblingFirstSub.Stage = 82; - } + } if (GamblingPlayerDice == GamblingNpcDice) { GamblingFirstSub.AllowItem = false; GamblingFirstSub.Stage = 83; - } + } } else if (SimpleDiceState == "win") { - GamblingFirstSub.Stage = 0; - ReputationProgress("Gambling", 1); + GamblingFirstSub.Stage = 0; + ReputationProgress("Gambling", 1); } else if (SimpleDiceState == "lost") { - InventoryWearRandom(Player, "ItemArms"); - GamblingFirstSub.Stage = 0; + InventoryWearRandom(Player, "ItemArms"); + GamblingFirstSub.Stage = 0; } else if (SimpleDiceState == "equal") { - InventoryRemove(Player, "ItemArms"); - InventoryRemove(GamblingFirstSub, "ItemArms"); - GamblingFirstSub.Stage = 0; + InventoryRemove(Player, "ItemArms"); + InventoryRemove(GamblingFirstSub, "ItemArms"); + GamblingFirstSub.Stage = 0; } } @@ -437,7 +437,7 @@ function GamblingTwentyOneController(TwentyOneState) { GamblingFirstSub.AllowItem = true; GamblingFirstSub.Stage = 0; ReputationProgress("Gambling", 3); - } + } GamblingPlayerDiceStack = []; GamblingNpcDiceStack = []; @@ -455,7 +455,7 @@ function GamblingTwentyOneController(TwentyOneState) { GamblingFirstSub.Appearance = GamblingAppearanceFirst.slice(); CharacterRefresh(GamblingFirstSub); GamblingFirstSub.Stage = 0; - } + } GamblingPlayerDiceStack = []; GamblingNpcDiceStack = []; @@ -472,75 +472,75 @@ function GamblingTwentyOneController(TwentyOneState) { * @param {"new" | "fox" | "hunter" | "NextDice" | "player_fox_win" | "player_fox_lost" | "player_hunter_lost" | "player_hunter_win"} FoxState - The current state of the game */ function GamblingFoxController(FoxState) { - if (FoxState == "new") { - GamblingPlayerDiceStack = []; - GamblingNpcDiceStack = []; - GamblingShowMoney = true; - } else if (FoxState == "fox") { - GamblingPlayerIsFox = true; - GamblingPlayerDice = Math.floor(Math.random() * 6) + 1; - GamblingPlayerDiceStack[GamblingPlayerDiceStack.length] = GamblingPlayerDice; - GamblingMoneyBet = 5; + if (FoxState == "new") { + GamblingPlayerDiceStack = []; + GamblingNpcDiceStack = []; + GamblingShowMoney = true; + } else if (FoxState == "fox") { + GamblingPlayerIsFox = true; + GamblingPlayerDice = Math.floor(Math.random() * 6) + 1; + GamblingPlayerDiceStack[GamblingPlayerDiceStack.length] = GamblingPlayerDice; + GamblingMoneyBet = 5; + GamblingSecondSub.Stage = 101; + } else if (FoxState == "hunter") { + GamblingPlayerIsFox = false; + GamblingMoneyBet = 5; + CharacterChangeMoney(Player, GamblingMoneyBet * -1); + GamblingNpcDice = Math.floor(Math.random() * 6) + 1; + GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice; + GamblingSecondSub.Stage = 101; + } else if (FoxState == "NextDice") { + GamblingPlayerDice = Math.floor(Math.random() * 6) + 1; + GamblingPlayerDiceStack[GamblingPlayerDiceStack.length] = GamblingPlayerDice; + GamblingNpcDice = Math.floor(Math.random() * 6) + 1; + GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice; + if (GamblingPlayerIsFox && GamblingDiceStackSum(GamblingPlayerDiceStack) >= 30) { + //player has won + GamblingSecondSub.Stage = 102; + } else if (!GamblingPlayerIsFox && GamblingDiceStackSum(GamblingNpcDiceStack) >= 30) { + //npc has won + GamblingSecondSub.Stage = 103; + } else if (GamblingPlayerIsFox && (GamblingDiceStackSum(GamblingPlayerDiceStack) <= GamblingDiceStackSum(GamblingNpcDiceStack))) { + //npc has won + GamblingSecondSub.Stage = 104; + } else if (!GamblingPlayerIsFox && (GamblingDiceStackSum(GamblingNpcDiceStack) <= GamblingDiceStackSum(GamblingPlayerDiceStack))) { + //player has won + GamblingSecondSub.Stage = 105; + } else { + //next dice GamblingSecondSub.Stage = 101; - } else if (FoxState == "hunter") { - GamblingPlayerIsFox = false; - GamblingMoneyBet = 5; - CharacterChangeMoney(Player, GamblingMoneyBet * -1); - GamblingNpcDice = Math.floor(Math.random() * 6) + 1; - GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice; - GamblingSecondSub.Stage = 101; - } else if (FoxState == "NextDice") { - GamblingPlayerDice = Math.floor(Math.random() * 6) + 1; - GamblingPlayerDiceStack[GamblingPlayerDiceStack.length] = GamblingPlayerDice; - GamblingNpcDice = Math.floor(Math.random() * 6) + 1; - GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice; - if (GamblingPlayerIsFox && GamblingDiceStackSum(GamblingPlayerDiceStack) >= 30) { - //player has won - GamblingSecondSub.Stage = 102; - } else if (!GamblingPlayerIsFox && GamblingDiceStackSum(GamblingNpcDiceStack) >= 30) { - //npc has won - GamblingSecondSub.Stage = 103; - } else if (GamblingPlayerIsFox && (GamblingDiceStackSum(GamblingPlayerDiceStack) <= GamblingDiceStackSum(GamblingNpcDiceStack))) { - //npc has won - GamblingSecondSub.Stage = 104; - } else if (!GamblingPlayerIsFox && (GamblingDiceStackSum(GamblingNpcDiceStack) <= GamblingDiceStackSum(GamblingPlayerDiceStack))) { - //player has won - GamblingSecondSub.Stage = 105; - } else { - //next dice - GamblingSecondSub.Stage = 101; - } - } else if (FoxState == "player_fox_win") { - GamblingSecondSub.AllowItem = false; - GamblingSecondSub.CurrentDialog = GamblingSecondSub.CurrentDialog.replace("REPLACEMONEY", GamblingMoneyBet.toString()); - CharacterChangeMoney(Player, GamblingMoneyBet); - ReputationProgress("Gambling", 2); - GamblingPlayerDiceStack = []; - GamblingNpcDiceStack = []; - GamblingShowMoney = false; - } else if (FoxState == "player_fox_lost") { - GamblingSecondSub.AllowItem = false; - InventoryWearRandom(Player, "ItemLegs"); - InventoryWearRandom(Player, "ItemFeet"); - InventoryWearRandom(Player, "ItemArms"); - GamblingPlayerDiceStack = []; - GamblingNpcDiceStack = []; - GamblingShowMoney = false; - } else if (FoxState == "player_hunter_win") { - InventoryWearRandom(GamblingSecondSub, "ItemArms"); - GamblingSecondSub.AllowItem = true; - GamblingSecondSub.CurrentDialog = GamblingSecondSub.CurrentDialog.replace("REPLACEMONEY", GamblingMoneyBet.toString()); - CharacterChangeMoney(Player, GamblingMoneyBet); - ReputationProgress("Gambling", 1); - GamblingPlayerDiceStack = []; - GamblingNpcDiceStack = []; - GamblingShowMoney = false; - } else if (FoxState == "player_hunter_lost") { - GamblingSecondSub.AllowItem = false; - GamblingPlayerDiceStack = []; - GamblingNpcDiceStack = []; - GamblingShowMoney = false; } + } else if (FoxState == "player_fox_win") { + GamblingSecondSub.AllowItem = false; + GamblingSecondSub.CurrentDialog = GamblingSecondSub.CurrentDialog.replace("REPLACEMONEY", GamblingMoneyBet.toString()); + CharacterChangeMoney(Player, GamblingMoneyBet); + ReputationProgress("Gambling", 2); + GamblingPlayerDiceStack = []; + GamblingNpcDiceStack = []; + GamblingShowMoney = false; + } else if (FoxState == "player_fox_lost") { + GamblingSecondSub.AllowItem = false; + InventoryWearRandom(Player, "ItemLegs"); + InventoryWearRandom(Player, "ItemFeet"); + InventoryWearRandom(Player, "ItemArms"); + GamblingPlayerDiceStack = []; + GamblingNpcDiceStack = []; + GamblingShowMoney = false; + } else if (FoxState == "player_hunter_win") { + InventoryWearRandom(GamblingSecondSub, "ItemArms"); + GamblingSecondSub.AllowItem = true; + GamblingSecondSub.CurrentDialog = GamblingSecondSub.CurrentDialog.replace("REPLACEMONEY", GamblingMoneyBet.toString()); + CharacterChangeMoney(Player, GamblingMoneyBet); + ReputationProgress("Gambling", 1); + GamblingPlayerDiceStack = []; + GamblingNpcDiceStack = []; + GamblingShowMoney = false; + } else if (FoxState == "player_hunter_lost") { + GamblingSecondSub.AllowItem = false; + GamblingPlayerDiceStack = []; + GamblingNpcDiceStack = []; + GamblingShowMoney = false; + } } /** @@ -672,9 +672,9 @@ function GamblingDaredSixController(DaredSixState) { } } else if (DaredSixState == "fin") { do { - GamblingNpcDice = Math.floor(Math.random() * 6) + 1; - GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice; - GamblingMoneyBet++; + GamblingNpcDice = Math.floor(Math.random() * 6) + 1; + GamblingNpcDiceStack[GamblingNpcDiceStack.length] = GamblingNpcDice; + GamblingMoneyBet++; } while (GamblingDiceStackSum(GamblingNpcDiceStack) <= GamblingDiceStackSum(GamblingPlayerDiceStack) && GamblingNpcDice != 6 ); if (GamblingNpcDice == 6) { diff --git a/BondageClub/Screens/Room/LewdgamblerEntrance/Dialog_NPC_Lewdgambler_Security.csv b/BondageClub/Screens/Room/LewdgamblerEntrance/Dialog_NPC_Lewdgambler_Security.csv new file mode 100644 index 0000000000..7942137f46 --- /dev/null +++ b/BondageClub/Screens/Room/LewdgamblerEntrance/Dialog_NPC_Lewdgambler_Security.csv @@ -0,0 +1,20 @@ +0,0,,"Greetings miss. Welcome to Mistress Lewdgambler Palace! Please, come closer. I have to check your member card before you can enter.",, +0,20,What is this place?,"So you’re a newcomer? Okay then, I will give you a quick tour. This is a closed gambling facility for people who are involved in domination relationships. You can play with other visitors, making a bets for money or bondage treats.",, +0,900,Here it is,--- > (checks the card and lets you in),GoToHall(),MembershipStatus(1) +0,0,What is a member card?,"This is a pass that proofs your membership status. It is required to enter a Palace. We want to make sure that our guest won’t be disturbed by narrow persons or vanillas, if you know what I mean.",,MembershipStatus() +0,10,I don’t have any cards,There is an option to buy one. But I need to do some background check first. What is your name again?,,MembershipStatus() +10,11,I’m DialogPlayerName,"Good. Give me a moment. … I see, you’re from the Bondage Club right?",, +11,12,I am.,Great then. 50$ is a price for a membership status. You will get an entry card signed by Mistress Lewdgambler personally.,, +12,900,Deal (pay 50$),Congratulations! Here is your pass and welcome to Lewdgamer Palace. Enjoy.,BuyMembership(),HasMoneyForMembership() +12,999,"That is too much, I don't have that money.","Sorry to say that, but I can’t let you in. This is some kind of closed facility and we do care of our guests comfort. Now I will ask you to leave. Good bye!",, +12,999,That is too much for a door pay.,"Sorry to say that, but I can’t let you in. This is some kind of closed facility and we do care of our guests comfort. Now I will ask you to leave. Good bye!",, +11,999,What is Bondage Club?,"Oh, my apologies. I believe some confusion have place in our data. Sorry to say that, but I can’t let you in. This is some kind of closed facility and we do care of our guests comfort. Now I will ask you to leave. Good bye!",, +999,0,(Leave),,DialogLeave();, +900,0,Thanks,,DialogLeave();, +20,21,(Next),To get in you also need to have a membership card.,, +21,0,Got it.,"Now please, present your member card. I have plenty of people waiting.",, +0,30,Is there an option to get in without a card?,Owners can take their slaves inside for free with some conditions. So if you’re owned by some Palace member then lucky you.,,MembershipStatus() +30,31,(Next),(giggles) Anyway slaves are not allowed to gamble at Palace. However any other way to have fun is allowed.,, +31,31,What are conditions for a slave?,"Pacified, collared, leashed, with strict hand restrains.",, +31,0,Got it.,"Now please, present your member card. I have plenty of people waiting.",, +0,0,(Leave),,DialogLeave();, diff --git a/BondageClub/Screens/Room/LewdgamblerEntrance/LewdgamblerEntrance.js b/BondageClub/Screens/Room/LewdgamblerEntrance/LewdgamblerEntrance.js new file mode 100644 index 0000000000..30cfd470f1 --- /dev/null +++ b/BondageClub/Screens/Room/LewdgamblerEntrance/LewdgamblerEntrance.js @@ -0,0 +1,63 @@ +'use strict'; + +(function _() { + const { + integrateRoomToWorld, + setPlayerGameData, + readPlayerGameData, + } = __vash_utils; + let securityGuy = null; + + const MEMBERSHIP_PRICE = 50; + const MEMBERSHIP_PROPERTY_KEY = 'Lewdgambler.isMember'; + const Background = 'LewdgamblerEntrance'; + + const Load = () => { + // Default load + if (securityGuy == null) { + securityGuy = CharacterLoadNPC('NPC_Lewdgambler_Security'); + securityGuy.Name = 'Security'; + CharacterNaked(securityGuy); + InventoryWear(securityGuy, 'CorsetShirt', 'Cloth', 'Default'); + InventoryWear(securityGuy, 'ShortPencilSkirt', 'ClothLower', 'Default'); + InventoryWear(securityGuy, 'Pantyhose1', 'Socks', 'Default'); + InventoryWear(securityGuy, 'GarterBelt2', 'Garters', 'Default'); + InventoryWear(securityGuy, 'Heels3', 'Shoes', '#202020'); + } + }; + + const Run = () => { + DrawCharacter(securityGuy, 750, 0, 1); + DrawCharacter(Player, 250, 0, 1); + if (Player.CanWalk()) DrawButton(1885, 25, 90, 90, '', 'White', 'Icons/Exit.png', TextGet("Leave")); + }; + + const Click = () => { + if (MouseIn(250, 0, 500, 1000)) CharacterSetCurrent(Player); + if (MouseIn(750, 0, 500, 1000)) CharacterSetCurrent(securityGuy); + if (MouseIn(1885, 25, 90, 90) && Player.CanWalk()) CommonSetScreen('Room', 'MainHall'); + }; + + const MembershipStatus = (requestedStatus) => { + const result = readPlayerGameData(MEMBERSHIP_PROPERTY_KEY) == requestedStatus; + return result; + }; + + const HasMoneyForMembership = () => + Player.Money >= MEMBERSHIP_PRICE; + + const BuyMembership = () => { + if (HasMoneyForMembership()) { + CharacterChangeMoney(Player, -MEMBERSHIP_PRICE); + setPlayerGameData('Lewdgambler.isMember', '1'); + } + }; + + const GoToHall = () => + CommonSetScreen('Room', 'LewdgamblerGameBiddingDuel'); + // CommonSetScreen('Room', 'LewdgamblerHall'); + + integrateRoomToWorld('LewdgamblerEntrance', { + Background, Load, Run, Click, BuyMembership, MembershipStatus, HasMoneyForMembership, GoToHall, + }); +})(); diff --git a/BondageClub/Screens/Room/LewdgamblerEntrance/Text_LewdgamblerEntrance.csv b/BondageClub/Screens/Room/LewdgamblerEntrance/Text_LewdgamblerEntrance.csv new file mode 100644 index 0000000000..fe8fba9ce3 --- /dev/null +++ b/BondageClub/Screens/Room/LewdgamblerEntrance/Text_LewdgamblerEntrance.csv @@ -0,0 +1 @@ +Leave,"Leave" diff --git a/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Bubble.png b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Bubble.png new file mode 100644 index 0000000000..eb898a4217 Binary files /dev/null and b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Bubble.png differ diff --git a/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Loose.csv b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Loose.csv new file mode 100644 index 0000000000..d4da58a1c5 --- /dev/null +++ b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Loose.csv @@ -0,0 +1,4 @@ +0,0,,"Oh dear! Looks like you lost it all. Such a poor girl.",, +0,1,"(Mumble in the gag.)","Oh stop it. Keep those noises for the visitors, I'll just take your money and will go rob someone else. Guards, take her away!" +1,2,"(Struggle in your restraints.)","[You lost this game completely. Opponent takes all money and leave you restrained to the will of Palace security.]" +2,0,"(Submit to guards.)",,"HandleLostGame()" diff --git a/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Win.csv b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Win.csv new file mode 100644 index 0000000000..1bfaccaeac --- /dev/null +++ b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Win.csv @@ -0,0 +1,2 @@ +0,0,,"[You won! (+50$)]" +0,0,"Yay!",,"HandleWonGame()" diff --git a/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/LewdgamblerGameBiddingDuel.js b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/LewdgamblerGameBiddingDuel.js new file mode 100644 index 0000000000..9b3f3027e2 --- /dev/null +++ b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/LewdgamblerGameBiddingDuel.js @@ -0,0 +1,345 @@ +'use strict'; + +(function _() { + const { + integrateRoomToWorld, + pipe, + } = __vash_utils; + + const { + INITIAL_STATE, + BID_ZONE, + STAGE, + addBid, + bidForOther, + canAddBid, + canRemoveBid, + removeBid, + setPileSize, + setStage, + switchStageAfterBids, + applyOtherDecision, + roundEnd, + restrain, + escape, + hasWinner, + } = __lewdgambler_biddingGame; + + let opponentGuy = null; + const Background = 'LewdgamblerRoomGameTable'; + const PILE_SIZE = 25; + + const lockWithTimer = (character, groupName) => { + const currentItem = InventoryGet(character, groupName); + if (!currentItem.Property || !currentItem.Property.LockedBy) { + InventoryLock(character, currentItem, { + Asset: AssetGet("Female3DCG", "ItemMisc", "TimerPadlock"), + }); + } + }; + + const releaseCharacter = (character) => { + InventoryRemove(character, 'ItemFeet'); + InventoryRemove(character, 'ItemBoots'); + InventoryRemove(character, 'ItemArms'); + InventoryRemove(character, 'ItemHands'); + InventoryRemove(character, 'ItemMouth'); + InventoryRemove(character, 'ItemMouth2'); + InventoryRemove(character, 'ItemHead'); + }; + + const applyEndgameStateTransition = (state, updatedState) => { + const isWinningTransition = !hasWinner(state) && hasWinner(updatedState); + if (!isWinningTransition) return; + + if (updatedState.stage === STAGE.PLAYER_LOST) { + CharacterSetCurrent(opponentGuy); + CharacterLoadCSVDialog(opponentGuy, 'Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Loose'); + releaseCharacter(opponentGuy); + } + + if (updatedState.stage === STAGE.PLAYER_WON) { + CharacterSetCurrent(opponentGuy); + CharacterLoadCSVDialog(opponentGuy, 'Screens/Room/LewdgamblerGameBiddingDuel/Dialog_NPC_LewdgamblerBiddingGame_Win'); + releaseCharacter(Player); + } + }; + + const applyRestrains = (character, restrains) => { + if (restrains[BID_ZONE.LEGS] > 0) { + InventoryWear(character, 'Irish8Cuffs', 'ItemFeet'); + } else { + InventoryRemove(character, 'ItemFeet'); + } + + if (restrains[BID_ZONE.LEGS] > 1) { + InventoryWear(character, 'BalletWedges', 'ItemBoots'); + } else { + InventoryRemove(character, 'ItemBoots'); + } + + if (restrains[BID_ZONE.LEGS] > 2) { + lockWithTimer(character, 'ItemFeet'); + lockWithTimer(character, 'ItemBoots'); + } else { + InventoryUnlock(character, 'ItemFeet'); + InventoryUnlock(character, 'ItemBoots'); + } + + if (restrains[BID_ZONE.HANDS] > 0) { + InventoryWear(character, 'CollarCuffs', 'ItemArms'); + } else { + InventoryRemove(character, 'ItemArms'); + } + + if (restrains[BID_ZONE.HANDS] > 1) { + lockWithTimer(character, 'ItemArms'); + } else { + InventoryUnlock(character, 'ItemArms'); + } + + if (restrains[BID_ZONE.HANDS] > 2) { + InventoryWear(character, 'PolishedMittens', 'ItemHands'); + } else { + InventoryRemove(character, 'ItemHands'); + } + + if (restrains[BID_ZONE.HEAD] > 0) { + InventoryWear(character, 'ClothGag', 'ItemMouth'); + } else { + InventoryRemove(character, 'ItemMouth'); + } + + if (restrains[BID_ZONE.HEAD] > 1) { + InventoryWear(character, 'MuzzleGag', 'ItemMouth2'); + } else { + InventoryRemove(character, 'ItemMouth2'); + } + + if (restrains[BID_ZONE.HEAD] > 2) { + InventoryWear(character, 'LeatherBlindfold', 'ItemHead'); + } else { + InventoryRemove(character, 'ItemHead'); + } + }; + + let gameState = INITIAL_STATE; + const check = selector => selector(gameState); + const setState = update => { + const updatedState = update(gameState); + applyRestrains(Player, updatedState.ownRestrains); + applyRestrains(opponentGuy, updatedState.otherRestrains); + applyEndgameStateTransition(gameState, updatedState); + gameState = updatedState; + }; + + const DrawCasinoButton = (top, left, w, h, text, hint) => { + const hintText = hint ? TextGet(hint) : null; + return DrawButton(top, left, w, h, text.toString(), '#61a66f', '', hintText, false, 'White', '#53885d'); + }; + + const Load = () => { + if (opponentGuy == null) { + opponentGuy = CharacterLoadNPC(''); + } + setState(() => INITIAL_STATE); + setState(setStage(STAGE.ROUND_START)); + setState(setPileSize(PILE_SIZE)); + }; + + const renderOwnBidValue = value => { + if (gameState.stage === STAGE.ROUND_START) return '-'; + return value; + }; + + const renderOpponentBidValue = value => { + if (gameState.stage === STAGE.ROUND_START) return '-'; + if (gameState.stage === STAGE.BIDDING) return '?'; + return value; + }; + + const renderOpponentBubble = text => { + DrawImage("Screens/" + CurrentModule + "/" + CurrentScreen + "/Bubble.png", 1500, 16); + DrawText(text, 1725, 53, "Black", "Gray"); + }; + + const Run = () => { + const { + otherBids, + otherPale, + ownBids, + ownPale, + stage, + winnerPale, + } = gameState; + + // characters preview + DrawCharacter(opponentGuy, 1500, 75, 0.9); + DrawCharacter(Player, 50, 75, 0.9); + + // Bidding labels + DrawTextFit(`${Player.Name} (${ownPale}$)`, 750, 295, 150, 'White', 'Black'); + DrawTextFit(`${opponentGuy.Name} (${otherPale}$)`, 750, 600, 150, 'White', 'Black'); + DrawTextFit('Head', 900, 80, 150, 'White', 'Black'); + DrawTextFit('Hands', 1075, 80, 150, 'White', 'Black'); + DrawTextFit('Feet', 1250, 80, 150, 'White', 'Black'); + + // Player bidding zone + DrawCasinoButton(855, 250, 90, 90, renderOwnBidValue(ownBids[BID_ZONE.HEAD])); + DrawCasinoButton(1030, 250, 90, 90, renderOwnBidValue(ownBids[BID_ZONE.HANDS])); + DrawCasinoButton(1205, 250, 90, 90, renderOwnBidValue(ownBids[BID_ZONE.LEGS])); + + // Opponent bidding zone + DrawCasinoButton(855, 555, 90, 90, renderOpponentBidValue(otherBids[BID_ZONE.HEAD])); + DrawCasinoButton(1030, 555, 90, 90, renderOpponentBidValue(otherBids[BID_ZONE.HANDS])); + DrawCasinoButton(1205, 555, 90, 90, renderOpponentBidValue(otherBids[BID_ZONE.LEGS])); + + // Player bid controls for bidding stage + if (stage === STAGE.BIDDING) { + if (check(canAddBid)) DrawCasinoButton(855, 140, 90, 90, '+', "AddHead"); + if (check(canRemoveBid(BID_ZONE.HEAD))) DrawCasinoButton(855, 360, 90, 90, '-', "RemoveHead"); + if (check(canAddBid)) DrawCasinoButton(1030, 140, 90, 90, '+', "AddHands"); + if (check(canRemoveBid(BID_ZONE.HANDS))) DrawCasinoButton(1030, 360, 90, 90, '-', "RemoveHands"); + if (check(canAddBid)) DrawCasinoButton(1205, 140, 90, 90, '+', "AddFeet"); + if (check(canRemoveBid(BID_ZONE.LEGS))) DrawCasinoButton(1205, 360, 90, 90, '-', "RemoveFeet"); + } + + // Winner's pale nocice + DrawTextFit(`Winner gains additional (${winnerPale}$)`, 875, 750, 400, 'White', 'Black'); + + // Generic actions zone + if (stage === STAGE.BIDDING) { + renderOpponentBubble('I\'m done. Your move?'); + DrawButton(1010, 850, 200, 90, 'Confirm', 'White', '', TextGet("ConfirmBidding")); + } + + if (stage === STAGE.ROUND_START) { + renderOpponentBubble('Scared, you chicken?'); + DrawButton(1010, 850, 200, 90, 'Go on', 'White', '', TextGet("StartRound")); + if (Player.CanWalk()) DrawButton(790, 850, 200, 90, 'Leave', 'White', '', TextGet("Leave")); + } + + if (stage === STAGE.WIN_DECISION) { + renderOpponentBubble('The round is yours'); + + // Release actions zone + DrawButton(75, 140, 400, 90, 'Release head', 'White', '', ''); + DrawButton(75, 250, 400, 90, 'Release hands', 'White', '', ''); + DrawButton(75, 360, 400, 90, 'Release feet', 'White', '', ''); + + // Restrain actions zone + DrawButton(1525, 140, 400, 90, 'Restrain head', 'White', '', ''); + DrawButton(1525, 250, 400, 90, 'Restrain hands', 'White', '', ''); + DrawButton(1525, 360, 400, 90, 'Restrain feet', 'White', '', ''); + } + + if (stage === STAGE.LOOSE_ACCEPTANCE) { + renderOpponentBubble('Looser, huh.'); + DrawButton(75, 690, 400, 90, 'Do your job...', 'White', '', ''); + } + }; + + const Click = () => { + const { + stage, + } = gameState; + + if (stage === STAGE.BIDDING) { + // bids controls + if (MouseIn(855, 140, 90, 90) && check(canAddBid)) setState(addBid(BID_ZONE.HEAD)); + if (MouseIn(855, 360, 90, 90) && check(canRemoveBid(BID_ZONE.HEAD))) setState(removeBid(BID_ZONE.HEAD)); + if (MouseIn(1030, 140, 90, 90) && check(canAddBid)) setState(addBid(BID_ZONE.HANDS)); + if (MouseIn(1030, 360, 90, 90) && check(canRemoveBid(BID_ZONE.HANDS))) setState(removeBid(BID_ZONE.HANDS)); + if (MouseIn(1205, 140, 90, 90) && check(canAddBid)) setState(addBid(BID_ZONE.LEGS)); + if (MouseIn(1205, 360, 90, 90) && check(canRemoveBid(BID_ZONE.LEGS))) setState(removeBid(BID_ZONE.LEGS)); + + // proceed buttons + if (MouseIn(1010, 850, 200, 90)) { + // cheat should go here + // setState(setStage(STAGE.CHEAT_DECISION)); + setState(switchStageAfterBids); + } + } + + if (stage === STAGE.ROUND_START) { + // leave button + if (MouseIn(790, 850, 200, 90)) { + if (Player.CanWalk()) CommonSetScreen('Room', 'MainHall'); + } + // proceed buttons + if (MouseIn(1010, 850, 200, 90)) { + setState(bidForOther); + setState(setStage(STAGE.BIDDING)); + } + } + + if (stage === STAGE.LOOSE_ACCEPTANCE) { + // loose acceptance buttons + if (MouseIn(75, 690, 400, 90)) { + setState(pipe( + applyOtherDecision, + roundEnd, + )); + } + } + + if (stage === STAGE.WIN_DECISION) { + // release buttons + if (MouseIn(75, 140, 400, 90)) { + setState(pipe( + escape(BID_ZONE.HEAD), + roundEnd, + )); + } + if (MouseIn(75, 250, 400, 90)) { + setState(pipe( + escape(BID_ZONE.HANDS), + roundEnd, + )); + } + if (MouseIn(75, 360, 400, 90)) { + setState(pipe( + escape(BID_ZONE.LEGS), + roundEnd, + )); + } + + // restrain buttons + if (MouseIn(1525, 140, 400, 90)) { + setState(pipe( + restrain(BID_ZONE.HEAD), + roundEnd, + )); + } + if (MouseIn(1525, 250, 400, 90)) { + setState(pipe( + restrain(BID_ZONE.HANDS), + roundEnd, + )); + } + if (MouseIn(1525, 360, 400, 90)) { + setState(pipe( + restrain(BID_ZONE.LEGS), + roundEnd, + )); + } + } + }; + + const HandleLostGame = () => { + // change to moving to casino hall so maids can't reach you + CommonSetScreen('Room', 'MainHall'); + CharacterChangeMoney(Player, -PILE_SIZE); + DialogLeave(); + }; + + const HandleWonGame = () => { + CommonSetScreen('Room', 'MainHall'); + CharacterChangeMoney(Player, PILE_SIZE * 2); + DialogLeave(); + }; + + integrateRoomToWorld('LewdgamblerGameBiddingDuel', { + Background, Load, Run, Click, HandleLostGame, HandleWonGame, + }); +})(); diff --git a/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/core.js b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/core.js new file mode 100644 index 0000000000..1eda2e130a --- /dev/null +++ b/BondageClub/Screens/Room/LewdgamblerGameBiddingDuel/core.js @@ -0,0 +1,520 @@ +"use strict"; + +/* eslint-disable semi */ +window.__lewdgambler_biddingGame = (function _() { + const { + pipe, + complement, + sum, + anyPass, + } = __vash_utils; + + const STAGE = { + INITIAL: 'INITIAL', + ROUND_START: 'ROUND_START', + BIDDING: 'BIDDING', + CHEAT_DECISION: 'CHEAT_DECISION', + WIN_DECISION: 'WIN_DECISION', + LOOSE_ACCEPTANCE: 'LOOSE_ACCEPTANCE', + PLAYER_WON: 'PLAYER_WON', + PLAYER_LOST: 'PLAYER_LOST', + } + + const CHEAT_CHANCE = 0.15 + const BLUFF_CHANCE = 0.15 + const MAX_RESTRAIN_LEVEL = 3 + + const RESTRAIN = { + HEAD_STUFFING: 'stuffing', + HEAD_CLEAVE: 'cleave', + HEAD_BLINDFOLD: 'blindfold', + FEET_TIED: 'tied-feet', + FEET_BOOTS: 'boots', + FEET_LOCKED: 'locked-feet', + HANDS_HANDCUFFS: 'handcuffs', + HANDS_TIED_BEHIND: 'armbinder', + HANDS_MITTENS: 'mittens', + } + + const BID_ZONE = { + HEAD: 'head', + HANDS: 'hands', + LEGS: 'legs', + } + + const DECISION = { + CHEAT_SKIP: 'CHEAT_SKIP', + CHEAT_HANDS: 'CHEAT_HANDS', + CHEAT_HEAD: 'CHEAT_HEAD', + CHEAT_FEET: 'CHEAT_FEET', + ESCAPE_HANDS: 'ESCAPE_HANDS', + ESCAPE_HEAD: 'ESCAPE_HEAD', + ESCAPE_FEET: 'ESCAPE_FEET', + RESTRAIN_HANDS: 'RESTRAIN_HANDS', + RESTRAIN_HEAD: 'RESTRAIN_HEAD', + RESTRAIN_FEET: 'RESTRAIN_FEET', + } + + const RESTRAIN_STATS = { + [RESTRAIN.HEAD_STUFFING]: { + zone: BID_ZONE.HEAD, + level: 1, + }, + [RESTRAIN.HEAD_CLEAVE]: { + zone: BID_ZONE.HEAD, + level: 2, + }, + [RESTRAIN.HEAD_BLINDFOLD]: { + zone: BID_ZONE.HEAD, + level: 3, + }, + [RESTRAIN.FEET_TIED]: { + zone: BID_ZONE.LEGS, + level: 1, + }, + [RESTRAIN.FEET_BOOTS]: { + zone: BID_ZONE.LEGS, + level: 2, + }, + [RESTRAIN.FEET_LOCKED]: { + zone: BID_ZONE.LEGS, + level: 3, + }, + [RESTRAIN.HANDS_HANDCUFFS]: { + zone: BID_ZONE.HANDS, + level: 1, + }, + [RESTRAIN.HANDS_TIED_BEHIND]: { + zone: BID_ZONE.HANDS, + level: 2, + }, + [RESTRAIN.HANDS_MITTENS]: { + zone: BID_ZONE.HANDS, + level: 3, + }, + } + + const BID_STATUS = { + WIN: 'win', + LOOSE: 'loose', + DRAW: 'draw', + } + + const INITIAL_STATE = { + ownPale: 0, + winnerPale: 0, + otherPale: 0, + stage: STAGE.INITIAL, + ownRestrains: { + [BID_ZONE.HEAD]: 0, + [BID_ZONE.HANDS]: 0, + [BID_ZONE.LEGS]: 1, + }, + otherRestrains: { + [BID_ZONE.HEAD]: 0, + [BID_ZONE.HANDS]: 0, + [BID_ZONE.LEGS]: 1, + }, + ownBids: { + [BID_ZONE.HEAD]: 0, + [BID_ZONE.HANDS]: 0, + [BID_ZONE.LEGS]: 0, + }, + otherBids: { + [BID_ZONE.HEAD]: 0, + [BID_ZONE.HANDS]: 0, + [BID_ZONE.LEGS]: 0, + }, + } + + const randomInt = (min, max) => + Math.floor(Math.random() * (max + 1 - min) + min) + + const checkRestrain = restrainName => restrains => { + const { zone, level } = (RESTRAIN_STATS[restrainName] || {}) + return (restrains[zone] || 0) >= level + } + + const hasRestrain = restrainName => state => + checkRestrain(restrainName)(state.ownRestrains); + const hasRestrains = restrains => state => + restrains.some(restrainName => hasRestrain(restrainName)(state)) + + const hasOtherRestrain = restrainName => state => + checkRestrain(restrainName)(state.otherRestrains); + const hasOtherRestrains = restrains => state => + restrains.some(restrainName => hasOtherRestrain(restrainName)(state)) + + // @TODO: add some intelligence here + const rollBid = pale => { + const max = Math.floor(pale / 3) + return { + [BID_ZONE.HEAD]: randomInt(0, max), + [BID_ZONE.HANDS]: randomInt(0, max), + [BID_ZONE.LEGS]: randomInt(0, max), + } + } + + const bidsTotal = bids => sum(Object.values(bids)) + + const canRestrain = zone => state => (state.otherRestrains[zone] || 0) < MAX_RESTRAIN_LEVEL + const canOtherRestrain = zone => state => (state.ownRestrains[zone] || 0) < MAX_RESTRAIN_LEVEL + const canEscape = zone => state => { + if (zone === BID_ZONE.LEGS && hasRestrains([RESTRAIN.FEET_LOCKED])(state)) { + return false + } + + return state.ownRestrains[zone] > 0 + } + const canOtherEscape = zone => state => { + if (zone === BID_ZONE.LEGS && hasOtherRestrains([RESTRAIN.FEET_LOCKED])(state)) { + return false + } + + return state.otherRestrains[zone] > 0 + } + const canLeave = complement(hasRestrains([RESTRAIN.FEET_TIED])) + const canCheat = complement(hasRestrains([RESTRAIN.HANDS_HANDCUFFS])) + const canBluff = complement(hasRestrains([RESTRAIN.HEAD_STUFFING])) + const canRemoveBid = zone => state => state.ownBids[zone] > 0 + const canAddBid = state => state.ownPale - bidsTotal(state.ownBids) > 0 + const isWinner = hasOtherRestrains([RESTRAIN.HEAD_BLINDFOLD, RESTRAIN.HANDS_MITTENS]) + const isLooser = hasRestrains([RESTRAIN.HEAD_BLINDFOLD, RESTRAIN.HANDS_MITTENS]) + const hasWinner = anyPass([isWinner, isLooser]) + const dropBids = state => ({ + ...state, + ownBids: { [BID_ZONE.HEAD]: 0, [BID_ZONE.HANDS]: 0, [BID_ZONE.LEGS]: 0 }, + otherBids: { [BID_ZONE.HEAD]: 0, [BID_ZONE.HANDS]: 0, [BID_ZONE.LEGS]: 0 }, + }) + const bidForOther = state => ({ + ...state, + otherBids: rollBid(state.otherPale), + }) + const addBid = zone => state => ({ + ...state, + ownBids: { + ...state.ownBids, + [zone]: state.ownBids[zone] + 1, + }, + }) + const removeBid = zone => state => ({ + ...state, + ownBids: { + ...state.ownBids, + [zone]: state.ownBids[zone] - 1, + }, + }) + + const cheat = zone => state => { + const cheatSucceed = Math.random() < CHEAT_CHANCE + if (!cheatSucceed) { + return state + } + const ownZoneBid = state.ownBids[zone] + const otherZoneBid = state.otherBids[zone] + const diff = otherZoneBid - ownZoneBid + const ownBidSum = bidsTotal(state.ownBids) + const addedBid = Math.min(state.ownPale - ownBidSum, diff + 1) + + return { + ...state, + ownBids: { + ...state.ownBids, + [zone]: ownZoneBid + addedBid, + }, + } + } + + const bluff = zone => state => { + const bluffSucceed = Math.random() < BLUFF_CHANCE + if (!bluffSucceed) { + return state + } + + const currentBid = state.otherBids + const paleLeft = state.otherPale - bidsTotal(state.otherBids) + const addedBid = randomInt(0, paleLeft) + + return { + ...state, + otherBids: { + ...state.otherBids, + [zone]: currentBid + addedBid, + }, + } + } + + const compareBids = zone => state => { + const ownBid = state.ownBids[zone] + const otherBid = state.otherBids[zone] + + if (ownBid === otherBid) return BID_STATUS.DRAW + if (ownBid > otherBid) return BID_STATUS.WIN + return BID_STATUS.LOOSE + } + + const getBidsStatuses = state => ({ + [BID_ZONE.HEAD]: compareBids(BID_ZONE.HEAD)(state), + [BID_ZONE.HANDS]: compareBids(BID_ZONE.HANDS)(state), + [BID_ZONE.LEGS]: compareBids(BID_ZONE.LEGS)(state), + }) + + const getBidRoundStatus = state => { + const statuses = getBidsStatuses(state) + const winsAmount = Object.values(statuses) + .filter(x => x === BID_STATUS.WIN).length + const lossesAmount = Object.values(statuses) + .filter(x => x === BID_STATUS.LOOSE).length + + if (winsAmount > lossesAmount) { + return BID_STATUS.WIN + } else if (winsAmount < lossesAmount) { + return BID_STATUS.LOOSE + } + + return BID_STATUS.DRAW + } + + const applyZoneBid = zone => state => { + const ownBid = state.ownBids[zone] + const otherBid = state.otherBids[zone] + const zoneStatus = compareBids(zone)(state) + + switch (zoneStatus) { + case BID_STATUS.DRAW: { + return { + ...state, + ownPale: Math.max(0, state.ownPale - ownBid), + otherPale: Math.max(0, state.otherPale - otherBid), + winnerPale: state.winnerPale + otherBid + ownBid, + } + } + case BID_STATUS.WIN: { + return { + ...state, + ownPale: Math.max(0, state.ownPale - ownBid), + otherPale: Math.max(0, state.otherPale - otherBid + ownBid), + winnerPale: state.winnerPale + otherBid, + } + } + case BID_STATUS.LOOSE: { + return { + ...state, + ownPale: Math.max(0, state.ownPale - ownBid + otherBid), + otherPale: Math.max(0, state.otherPale - otherBid), + winnerPale: state.winnerPale + ownBid, + } + } + default: return state + } + } + + const recalculatePales = state => Object.values(BID_ZONE) + .reduce((acc, zone) => applyZoneBid(zone)(acc), state) + + const applyBids = pipe( + recalculatePales, + dropBids, + ) + + const restrain = zone => state => ({ + ...state, + otherRestrains: { + ...state.otherRestrains, + [zone]: state.otherRestrains[zone] + 1, + }, + }) + + const otherRestrain = zone => state => ({ + ...state, + ownRestrains: { + ...state.ownRestrains, + [zone]: state.ownRestrains[zone] + 1, + }, + }) + + const escape = zone => state => ({ + ...state, + ownRestrains: { + ...state.ownRestrains, + [zone]: state.ownRestrains[zone] - 1, + }, + }) + + const otherEscape = zone => state => ({ + ...state, + otherRestrains: { + ...state.otherRestrains, + [zone]: state.otherRestrains[zone] - 1, + }, + }) + + const rollLooseDecision = state => { + const bidsStatus = getBidsStatuses(state) + + const wins = { + [BID_ZONE.HANDS]: bidsStatus[BID_ZONE.HANDS] === BID_STATUS.LOOSE, + [BID_ZONE.HEAD]: bidsStatus[BID_ZONE.HEAD] === BID_STATUS.LOOSE, + [BID_ZONE.LEGS]: bidsStatus[BID_ZONE.LEGS] === BID_STATUS.LOOSE, + } + + const options = [ + wins[BID_ZONE.HANDS] + && canOtherEscape(BID_ZONE.HANDS)(state) + && DECISION.ESCAPE_HANDS, + wins[BID_ZONE.HEAD] + && canOtherEscape(BID_ZONE.HEAD)(state) + && DECISION.ESCAPE_HEAD, + wins[BID_ZONE.LEGS] + && canOtherEscape(BID_ZONE.LEGS)(state) + && DECISION.ESCAPE_FEET, + wins[BID_ZONE.HANDS] + && canOtherRestrain(BID_ZONE.HANDS)(state) + && DECISION.RESTRAIN_HANDS, + wins[BID_ZONE.HEAD] + && canOtherRestrain(BID_ZONE.HEAD)(state) + && DECISION.RESTRAIN_HEAD, + wins[BID_ZONE.LEGS] + && canOtherRestrain(BID_ZONE.LEGS)(state) + && DECISION.RESTRAIN_FEET, + ].filter(Boolean) + + const decisionIndex = randomInt(0, options.length - 1) + return options[decisionIndex] + } + + const getCheatDecisions = state => canCheat(state) + ? [DECISION.CHEAT_HANDS, DECISION.CHEAT_HEAD, DECISION.CHEAT_FEET, DECISION.CHEAT_SKIP] + : [DECISION.CHEAT_SKIP] + + const getWinDecisions = state => { + const bidsStatus = getBidsStatuses(state) + + const wins = { + [BID_ZONE.HANDS]: bidsStatus[BID_ZONE.HANDS] === BID_STATUS.WIN, + [BID_ZONE.HEAD]: bidsStatus[BID_ZONE.HEAD] === BID_STATUS.WIN, + [BID_ZONE.LEGS]: bidsStatus[BID_ZONE.LEGS] === BID_STATUS.WIN, + } + + return [ + wins[BID_ZONE.HANDS] + && canEscape(BID_ZONE.HANDS)(state) + && DECISION.ESCAPE_HANDS, + wins[BID_ZONE.HEAD] + && canEscape(BID_ZONE.HEAD)(state) + && DECISION.ESCAPE_HEAD, + wins[BID_ZONE.LEGS] + && canEscape(BID_ZONE.LEGS)(state) + && DECISION.ESCAPE_FEET, + wins[BID_ZONE.HANDS] + && canRestrain(BID_ZONE.HANDS)(state) + && DECISION.RESTRAIN_HANDS, + wins[BID_ZONE.HEAD] + && canRestrain(BID_ZONE.HEAD)(state) + && DECISION.RESTRAIN_HEAD, + wins[BID_ZONE.LEGS] + && canRestrain(BID_ZONE.LEGS)(state) + && DECISION.RESTRAIN_FEET, + ].filter(Boolean) + } + + const setStage = stage => state => ({ ...state, stage }) + const setPileSize = pileSize => state => ({ + ...state, + ownPale: pileSize, + otherPale: pileSize, + }) + + const switchStageAfterBids = state => { + const roundStatus = getBidRoundStatus(state) + const newStage = roundStatus === BID_STATUS.DRAW + ? STAGE.ROUND_START + : roundStatus === BID_STATUS.WIN + ? STAGE.WIN_DECISION + : STAGE.LOOSE_ACCEPTANCE + + return setStage(newStage)(state) + } + + const applyOwnDecision = decision => state => { + switch (decision) { + case DECISION.RESTRAIN_HEAD: + return restrain(BID_ZONE.HEAD)(state) + case DECISION.RESTRAIN_HANDS: + return restrain(BID_ZONE.HANDS)(state) + case DECISION.RESTRAIN_FEET: + return restrain(BID_ZONE.LEGS)(state) + case DECISION.ESCAPE_HEAD: + return escape(BID_ZONE.HEAD)(state) + case DECISION.ESCAPE_HANDS: + return escape(BID_ZONE.HANDS)(state) + case DECISION.ESCAPE_FEET: + return escape(BID_ZONE.LEGS)(state) + default: return state + } + } + + const applyOtherDecision = state => { + const decision = rollLooseDecision(state) + + switch (decision) { + case DECISION.RESTRAIN_HEAD: + return otherRestrain(BID_ZONE.HEAD)(state) + case DECISION.RESTRAIN_HANDS: + return otherRestrain(BID_ZONE.HANDS)(state) + case DECISION.RESTRAIN_FEET: + return otherRestrain(BID_ZONE.LEGS)(state) + case DECISION.ESCAPE_HEAD: + return otherEscape(BID_ZONE.HEAD)(state) + case DECISION.ESCAPE_HANDS: + return otherEscape(BID_ZONE.HANDS)(state) + case DECISION.ESCAPE_FEET: + return otherEscape(BID_ZONE.LEGS)(state) + default: return state + } + } + + const roundEnd = state => { + const newStage = isWinner(state) + ? STAGE.PLAYER_WON + : isLooser(state) + ? STAGE.PLAYER_LOST + : STAGE.ROUND_START + + return pipe( + setStage(newStage), + applyBids, + )(state) + } + + return { + BID_ZONE, + DECISION, + INITIAL_STATE, + RESTRAIN, + STAGE, + addBid, + applyBids, + applyOtherDecision, + applyOwnDecision, + bidForOther, + bluff, + canAddBid, + canBluff, + canLeave, + canRemoveBid, + cheat, + escape, + getBidRoundStatus, + getCheatDecisions, + getWinDecisions, + removeBid, + restrain, + roundEnd, + setPileSize, + setStage, + switchStageAfterBids, + checkRestrain, + hasWinner, + } +})(); diff --git a/BondageClub/Screens/Room/LewdgamblerHall/Dialog_NPC_Lewdgambler_Hostess.csv b/BondageClub/Screens/Room/LewdgamblerHall/Dialog_NPC_Lewdgambler_Hostess.csv new file mode 100644 index 0000000000..e69de29bb2 diff --git a/BondageClub/Screens/Room/LewdgamblerHall/LewdgamblerHall.js b/BondageClub/Screens/Room/LewdgamblerHall/LewdgamblerHall.js new file mode 100644 index 0000000000..a6c0999977 --- /dev/null +++ b/BondageClub/Screens/Room/LewdgamblerHall/LewdgamblerHall.js @@ -0,0 +1,62 @@ +'use strict'; + +(function _() { + const { + integrateRoomToWorld, + setPlayerGameData, + readPlayerGameData, + } = __vash_utils; + let hostessGuy = null; + + const MEMBERSHIP_PRICE = 50; + const MEMBERSHIP_PROPERTY_KEY = 'Lewdgambler.isMember'; + const Background = 'LewdgamblerHall'; + + const Load = () => { + // Default load + if (hostessGuy == null) { + hostessGuy = CharacterLoadNPC('NPC_Lewdgambler_Hostess'); + hostessGuy.Name = 'Hostess girl'; + CharacterNaked(hostessGuy); + InventoryWear(hostessGuy, 'CorsetShirt', 'Cloth', 'Default'); + InventoryWear(hostessGuy, 'ShortPencilSkirt', 'ClothLower', 'Default'); + InventoryWear(hostessGuy, 'Pantyhose1', 'Socks', 'Default'); + InventoryWear(hostessGuy, 'GarterBelt2', 'Garters', 'Default'); + InventoryWear(hostessGuy, 'Heels3', 'Shoes', '#202020'); + } + }; + + const Run = () => { + DrawCharacter(hostessGuy, 750, 0, 1); + DrawCharacter(Player, 250, 0, 1); + if (Player.CanWalk()) DrawButton(1885, 25, 90, 90, '', 'White', 'Icons/Exit.png', TextGet("Leave")); + }; + + const Click = () => { + if (MouseIn(250, 0, 500, 1000)) CharacterSetCurrent(Player); + if (MouseIn(750, 0, 500, 1000)) CharacterSetCurrent(hostessGuy); + if (MouseIn(1885, 25, 90, 90) && Player.CanWalk()) CommonSetScreen('Room', 'MainHall'); + }; + + const MembershipStatus = (requestedStatus) => { + const result = readPlayerGameData(MEMBERSHIP_PROPERTY_KEY) == requestedStatus; + return result; + }; + + const HasMoneyForMembership = () => + Player.Money >= MEMBERSHIP_PRICE; + + const BuyMembership = () => { + if (HasMoneyForMembership()) { + CharacterChangeMoney(Player, -MEMBERSHIP_PRICE); + setPlayerGameData('Lewdgambler.isMember', '1'); + } + }; + + const GoToHall = () => + CommonSetScreen('Room', 'LewdgamblerHall'); + + integrateRoomToWorld('LewdgamblerHall', { + Background, Load, Run, Click, BuyMembership, MembershipStatus, HasMoneyForMembership, GoToHall, + }); +})(); diff --git a/BondageClub/Screens/Room/LewdgamblerHall/Text_LewdgamblerHall.csv b/BondageClub/Screens/Room/LewdgamblerHall/Text_LewdgamblerHall.csv new file mode 100644 index 0000000000..fe8fba9ce3 --- /dev/null +++ b/BondageClub/Screens/Room/LewdgamblerHall/Text_LewdgamblerHall.csv @@ -0,0 +1 @@ +Leave,"Leave" diff --git a/BondageClub/Screens/Room/MainHall/MainHall.js b/BondageClub/Screens/Room/MainHall/MainHall.js index b9977d85d0..3793793a54 100644 --- a/BondageClub/Screens/Room/MainHall/MainHall.js +++ b/BondageClub/Screens/Room/MainHall/MainHall.js @@ -239,6 +239,7 @@ function MainHallRun() { DrawButton(1885, 505, 90, 90, "", "White", "Icons/Cell.png", TextGet("Cell")); // Asylum, College & LARP battles + DrawButton(1525, 625, 90, 90, "", "White", "Icons/Lewdgambler.png", TextGet("Lewdgambler")); if (!ManagementIsClubSlave()) DrawButton(1645, 625, 90, 90, "", "White", "Icons/Battle.png", TextGet("LARPBattle")); if (!ManagementIsClubSlave()) DrawButton(1765, 625, 90, 90, "", "White", "Icons/College.png", TextGet("College")); DrawButton(1885, 625, 90, 90, "", "White", "Icons/Asylum.png", TextGet("Asylum")); @@ -389,6 +390,8 @@ function MainHallClick() { if ((MouseX >= 1885) && (MouseX < 1975) && (MouseY >= 505) && (MouseY < 595)) MainHallWalk("Cell"); // Asylum & College + // if ((MouseX >= 1525) && (MouseX < 1615) && (MouseY >= 625) && (MouseY < 715)) MainHallWalk("LewdgamblerEntrance"); + if ((MouseX >= 1525) && (MouseX < 1615) && (MouseY >= 625) && (MouseY < 715)) MainHallWalk("LewdgamblerGameBiddingDuel"); if ((MouseX >= 1645) && (MouseX < 1735) && (MouseY >= 625) && (MouseY < 715) && !ManagementIsClubSlave()) MainHallWalk("LARP"); if ((MouseX >= 1765) && (MouseX < 1855) && (MouseY >= 625) && (MouseY < 715) && !ManagementIsClubSlave()) MainHallWalk("CollegeEntrance"); if ((MouseX >= 1885) && (MouseX < 1975) && (MouseY >= 625) && (MouseY < 715)) MainHallWalk("AsylumEntrance"); diff --git a/BondageClub/Screens/Room/MainHall/Text_MainHall.csv b/BondageClub/Screens/Room/MainHall/Text_MainHall.csv index 212e3bdca7..a744eadba3 100644 --- a/BondageClub/Screens/Room/MainHall/Text_MainHall.csv +++ b/BondageClub/Screens/Room/MainHall/Text_MainHall.csv @@ -16,6 +16,7 @@ Appearance,Change Appearance, Cell,Timer Cell, SlaveMarket,Slave Market, LookForTrouble,Look for Trouble, +Lewdgambler, Lewdgambler Palace, Asylum,Asylum, College,Visit the College, LARPBattle,LARP Battles, diff --git a/BondageClub/Screens/Room/utils.js b/BondageClub/Screens/Room/utils.js new file mode 100644 index 0000000000..a489d07266 --- /dev/null +++ b/BondageClub/Screens/Room/utils.js @@ -0,0 +1,61 @@ +"use strict"; + +window.__vash_utils = (function _() { + const randomInt = (min, max) => + Math.floor(Math.random() * (max + 1 - min) + min); + + const integrateRoomToWorld = (roomName, props) => { + Object.entries(props).forEach(([prop, value]) => { + const key = `${roomName}${prop}`; + if (window[key] !== undefined) { + console.warn(`Attempt to override window property "${key}" while integrating room "${roomName}"`); + } else { + window[key] = value; + } + }); + }; + + const setPlayerGameData = (path, value) => { + const pathChains = path.split('.'); + if (!Player.Game) { + Player.Game = {}; + } + let pointer = Player.Game; + while (pathChains.length > 1) { + const key = pathChains.shift(); + if (!pointer[key]) pointer[key] = {}; + pointer = pointer[key]; + } + pointer[pathChains[0]] = value; + ServerAccountUpdate.QueueData({ Game: Player.Game }, true); + }; + + const readPlayerGameData = (path) => { + const pathChains = path.split('.'); + if (!Player.Game) { + Player.Game = {}; + } + return pathChains.reduce((pointer, key) => pointer && pointer[key], Player.Game); + }; + + const getRandomItem = (array) => { + const index = randomInt(0, array.length - 1); + return array[index]; + }; + + const sum = list => list.reduce((a, i) => a + i, 0); + const complement = predicate => (...props) => !predicate(...props); + const pipe = (...funcs) => argument => funcs.reduce((arg, func) => func(arg), argument); + const anyPass = (predicates) => (...props) => predicates.some(p => p(...props)); + + return { + integrateRoomToWorld, + setPlayerGameData, + readPlayerGameData, + getRandomItem, + sum, + complement, + pipe, + anyPass, + }; +})(); diff --git a/BondageClub/Scripts/Drawing.js b/BondageClub/Scripts/Drawing.js index 6361d050fc..3022d00dd4 100644 --- a/BondageClub/Scripts/Drawing.js +++ b/BondageClub/Scripts/Drawing.js @@ -954,35 +954,41 @@ function DrawText(Text, X, Y, Color, BackColor) { * @param {number} Width - Width of the component * @param {number} Height - Height of the component * @param {string} Label - Text to display in the button - * @param {string} Color - Color of the component + * @param {string} BackgroundColor - Color of the component * @param {string} [Image] - URL of the image to draw inside the button, if applicable * @param {string} [HoveringText] - Text of the tooltip, if applicable * @param {boolean} [Disabled] - Disables the hovering options if set to true + * @param {string} [TextColor] - Foreground Color of the component + * @param {string} [HoverColor] - Hovered state color of the component * @returns {void} - Nothing */ -function DrawButton(Left, Top, Width, Height, Label, Color, Image, HoveringText, Disabled) { +function DrawButton(Left, Top, Width, Height, Label, BackgroundColor, Image, HoveringText, Disabled, TextColor, HoverColor) { if (ControllerActive == true) { setButton(Left, Top); } + const foregroundColor = TextColor || "black"; + const accentColor = HoverColor || "Cyan"; + const isHovered = (MouseX >= Left) && (MouseX <= Left + Width) && (MouseY >= Top) && (MouseY <= Top + Height); + const hoverEnabled = HoveringText != null; // Draw the button rectangle (makes the background color cyan if the mouse is over it) MainCanvas.beginPath(); MainCanvas.rect(Left, Top, Width, Height); - MainCanvas.fillStyle = ((MouseX >= Left) && (MouseX <= Left + Width) && (MouseY >= Top) && (MouseY <= Top + Height) && !CommonIsMobile && !Disabled) ? "Cyan" : Color; + MainCanvas.fillStyle = (isHovered && hoverEnabled && !CommonIsMobile && !Disabled) ? accentColor : BackgroundColor; MainCanvas.fillRect(Left, Top, Width, Height); MainCanvas.fill(); MainCanvas.lineWidth = 2; - MainCanvas.strokeStyle = 'black'; + MainCanvas.strokeStyle = foregroundColor; MainCanvas.stroke(); MainCanvas.closePath(); // Draw the text or image - DrawTextFit(Label, Left + Width / 2, Top + (Height / 2) + 1, Width - 4, "black"); + DrawTextFit(Label, Left + Width / 2, Top + (Height / 2) + 1, Width - 4, foregroundColor); if ((Image != null) && (Image != "")) DrawImage(Image, Left + 2, Top + 2); // Draw the hovering text - if ((HoveringText != null) && (MouseX >= Left) && (MouseX <= Left + Width) && (MouseY >= Top) && (MouseY <= Top + Height) && !CommonIsMobile) { + if (hoverEnabled && isHovered && !CommonIsMobile) { DrawHoverElements.push(() => DrawButtonHover(Left, Top, Width, Height, HoveringText)); } } diff --git a/BondageClub/index.html b/BondageClub/index.html index 22b1ecf867..be0d27e187 100644 --- a/BondageClub/index.html +++ b/BondageClub/index.html @@ -78,6 +78,7 @@ <script src="Screens/Character/Title/Title.js"></script> <script src="Screens/Character/BackgroundSelection/BackgroundSelection.js"></script> <script src="Screens/Character/Relog/Relog.js"></script> +<script src="Screens/Room/utils.js"></script> <script src="Screens/Room/MainHall/MainHall.js"></script> <script src="Screens/Room/Shop/Shop.js"></script> <script src="Screens/Room/Introduction/Introduction.js"></script> @@ -122,6 +123,9 @@ <script src="Screens/Room/Nursery/Nursery.js"></script> <script src="Screens/Room/Cafe/Cafe.js"></script> <script src="Screens/Room/Arcade/Arcade.js"></script> +<script src="Screens/Room/LewdgamblerEntrance/LewdgamblerEntrance.js"></script> +<script src="Screens/Room/LewdgamblerGameBiddingDuel/core.js"></script> +<script src="Screens/Room/LewdgamblerGameBiddingDuel/LewdgamblerGameBiddingDuel.js"></script> <script src="Screens/MiniGame/HorseWalk/HorseWalk.js"></script> <script src="Screens/MiniGame/GetUp/GetUp.js"></script> <script src="Screens/MiniGame/Kidnap/Kidnap.js"></script>