1 /**********************************************************************************
2 * Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC
3 * Copyright (c) 2010, Moloch
4 * Copyright (c) 2018, Ketmar Dark
6 * This file is part of Spelunky.
8 * You can redistribute and/or modify Spelunky, including its source code, under
9 * the terms of the Spelunky User License.
11 * Spelunky is distributed in the hope that it will be entertaining and useful,
12 * but WITHOUT WARRANTY. Please see the Spelunky User License for more details.
14 * The Spelunky User License should be available in "Game Information", which
15 * can be found in the Resource Explorer, or as an external file called COPYING.
16 * If not, please obtain a new copy of Spelunky from <http://spelunkyworld.com/>
18 **********************************************************************************/
19 class ItemDice['oDice'] : MapItem;
22 None, // ready to roll
24 Finished, // landed in shop
25 Failed, // landed outside of the shop
29 bool pickedOutsideOfAShop;
32 override bool initialize () {
33 if (!::initialize()) return false;
35 value = global.randOther(1, 6);
41 final int getRollNumber () {
42 if (rollState != RollState.Finished) return 0;
47 final void resetRollState () {
48 rollState = RollState.None;
52 bool isReadyToThrowForBet () {
53 if (!forSale) return false;
54 return ((rollState == RollState.Failed || rollState == RollState.None) && level.player.bet);
58 bool isInsideCrapsShop () {
59 if (!level.isInShop(ix/16, iy/16)) return false;
60 auto sst = level.lg.roomShopType(ix/16, iy/16);
61 return (sst == 'Craps');
65 // various side effects
66 // called only if object was succesfully put into player hands
67 override void onPickedUp (PlayerPawn plr) {
68 pickedOutsideOfAShop = !isInsideCrapsShop();
69 if (rollState != RollState.Finished) rollState = RollState.None;
73 override void drawSignsWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
75 if ((rollState == RollState.Failed || rollState == RollState.None) && level.player.bet) {
77 getInterpCoords(currFrameDelta, scale, out xi, out yi);
78 auto spr = level.sprStore['sRedArrowDown'];
79 if (spr && spr.frames.length) {
80 auto spf = spr.frames[0];
81 spf.blitAt(xi-xpos-spf.xofs*scale, yi-ypos-(12+spf.yofs)*scale, scale);
87 override void onCheckItemStolen (PlayerPawn plr) {
88 if (!heldBy || pickedOutsideOfAShop) return;
89 if (forSale && !level.player.bet && rollState != RollState.Failed) {
90 bool inShop = level.isInShop(ix/16, iy/16);
92 level.scrShopkeeperAnger(GameLevel::SCAnger.ItemStolen); // don't steal it!
98 override void thinkFrame () {
99 if (forSale && /*!forVending && cost > 0 &&*/ !level.hasAliveShopkeepers(skipAngry:true)) {
104 //lightRadius = max(origLightRadius, (forSale && level.isInShop(ix/16, iy/16) ? 64 : 0));
105 if (forSale && level.isInShop(ix/16, iy/16)) {
106 lightRadius = max(default.lightRadius, 64);
108 lightRadius = default.lightRadius;
113 if (oCharacter.facing == LEFT) x = oCharacter.x - 4;
114 else if (oCharacter.facing == RIGHT) x = oCharacter.x + 4;
117 if (oCharacter.state == DUCKING and abs(oCharacter.xVel) < 2) y = oCharacter.y; else y = oCharacter.y-2;
119 if (oCharacter.state == DUCKING and abs(oCharacter.xVel) < 2) y = oCharacter.y+4; else y = oCharacter.y+2;
123 if (oCharacter.holdItem == 0) held = false;
125 // stealing makes shopkeeper angry
126 //writeln("!!! fs=", forSale, "; bet=", level.player.bet, "; st=", rollState);
130 bool colLeft = !!isCollisionLeft(1);
131 bool colRight = !!isCollisionRight(1);
132 bool colBot = !!isCollisionBottom(1);
133 //bool colTop = !!isCollisionTop(1);
135 if (!colBot && yVel < 6) yVel += myGrav;
137 if (fabs(xVel) < 0.1) xVel = 0;
138 else if (colLeft || colRight) xVel = -xVel*0.5;
142 if (yVel > 1) { ++bounceCount; yVel = -yVel*bounceFactor; } else yVel = 0;
144 if (fabs(xVel) < 0.1) xVel = 0;
145 else if (fabs(xVel) != 0) xVel *= frictionFactor;
146 if (fabs(yVel) < 1) {
148 if (!isCollisionBottom(1)) flty += 1;
154 if (!colRight) fltx += 1;
156 } else if (colRight) {
161 if (isCollisionTop(1)) {
162 if (yVel < 0) yVel = -yVel*0.8; else flty += 1;
165 //!depth = (global.hasSpectacles ? 0 : 101); //???
167 if (isCollisionInRect(ix-3, iy-3, 7, 7, &level.cbCollisionLava)) {
176 if (isCollisionAtPoint(ix, iy-5, &level.cbCollisionLava)) {
182 if (!isInstanceAlive || spectral) return;
184 if (fabs(xVel) > 3 || fabs(yVel) > 3) {
186 auto plr = level.player;
187 if (plr.isRectCollision(ix+enemyColX, iy+enemyColY, enemyColW, enemyColH)) {
188 doPlayerColAction(plr);
192 level.forEachObjectInRect(ix-2, iy-2, 5, 5, &doObjectColAction);
197 if (fabs(yVel) > 2 || fabs(xVel) > 2) {
198 setSprite('sDiceRoll');
199 if (rollState != RollState.Rolling) {
201 value = global.randOther(1, 6);
202 //writeln("DICE: oldval=", ost, "; newval=", value);
205 case RollState.Finished:
207 if (level.player.bet > 0) level.scrShopkeeperAnger(GameLevel::SCAnger.CrapsCheated);
210 rollState = RollState.Rolling;
214 if (yVel == 0 && fabs(xVel <= 2) && isCollisionBottom(1)) {
217 case RollState.Rolling:
218 rollState = (isInsideCrapsShop() ? RollState.Finished : RollState.Failed);
219 if (rollState == RollState.Finished) level.player.onDieRolled(self);
221 case RollState.Finished:
222 case RollState.Failed:
225 default: rollState = RollState.None; break;
230 if (rollState != RollState.Rolling) {
232 case 1: setSprite('sDice1'); break;
233 case 2: setSprite('sDice2'); break;
234 case 3: setSprite('sDice3'); break;
235 case 4: setSprite('sDice4'); break;
236 case 5: setSprite('sDice5'); break;
237 default: setSprite('sDice6'); break;
241 canPickUp = (rollState != RollState.Rolling);
248 desc2 = "A six-sided die. The storeowner talks to it every night before he goes to sleep.";
249 setCollisionBounds(-6, 0, 6, 8);
257 rollState = RollState.None;
258 bloodless = true; // just in case, lol
259 canHitEnemies = true;
260 sellingToShopAllowed = true;