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;
23 None, // ready to roll
25 Finished, // landed in shop
26 Failed, // landed outside of the shop
30 bool pickedOutsideOfAShop;
33 override bool initialize () {
34 if (!::initialize()) return false;
36 value = global.randOther(1, 6);
42 final int getRollNumber () {
43 if (rollState != RollState.Finished) return 0;
48 final void resetRollState () {
49 rollState = RollState.None;
53 bool isReadyToThrowForBet () {
54 if (!forSale) return false;
55 return ((rollState == RollState.Failed || rollState == RollState.None) && level.player.bet);
59 bool isInsideCrapsShop () {
60 if (!level.isInShop(ix/16, iy/16)) return false;
61 auto sst = level.lg.roomShopType(ix/16, iy/16);
62 return (sst == 'Craps');
66 // various side effects
67 // called only if object was succesfully put into player hands
68 override void onPickedUp (PlayerPawn plr) {
69 pickedOutsideOfAShop = !isInsideCrapsShop();
70 if (rollState != RollState.Finished) rollState = RollState.None;
74 override void drawSignsWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
76 if ((rollState == RollState.Failed || rollState == RollState.None) && level.player.bet) {
78 getInterpCoords(currFrameDelta, scale, out xi, out yi);
79 auto spr = level.sprStore['sRedArrowDown'];
80 if (spr && spr.frames.length) {
81 auto spf = spr.frames[0];
82 spf.tex.blitAt(xi-xpos-spf.xofs*scale, yi-ypos-(12+spf.yofs)*scale, scale);
88 override void onCheckItemStolen (PlayerPawn plr) {
89 if (!heldBy || pickedOutsideOfAShop) return;
90 if (forSale && !level.player.bet && rollState != RollState.Failed) {
91 bool inShop = level.isInShop(ix/16, iy/16);
93 level.scrShopkeeperAnger(GameLevel::SCAnger.ItemStolen); // don't steal it!
99 override void thinkFrame () {
100 if (forSale && /*!forVending && cost > 0 &&*/ !level.hasAliveShopkeepers(skipAngry:true)) {
105 lightRadius = max(origLightRadius, (forSale && level.isInShop(ix/16, iy/16) ? 64 : 0));
109 if (oCharacter.facing == LEFT) x = oCharacter.x - 4;
110 else if (oCharacter.facing == RIGHT) x = oCharacter.x + 4;
113 if (oCharacter.state == DUCKING and abs(oCharacter.xVel) < 2) y = oCharacter.y; else y = oCharacter.y-2;
115 if (oCharacter.state == DUCKING and abs(oCharacter.xVel) < 2) y = oCharacter.y+4; else y = oCharacter.y+2;
119 if (oCharacter.holdItem == 0) held = false;
121 // stealing makes shopkeeper angry
122 //writeln("!!! fs=", forSale, "; bet=", level.player.bet, "; st=", rollState);
126 bool colLeft = !!isCollisionLeft(1);
127 bool colRight = !!isCollisionRight(1);
128 bool colBot = !!isCollisionBottom(1);
129 bool colTop = !!isCollisionTop(1);
131 if (!colBot && yVel < 6) yVel += myGrav;
133 if (fabs(xVel) < 0.1) xVel = 0;
134 else if (colLeft || colRight) xVel = -xVel*0.5;
138 if (yVel > 1) { ++bounceCount; yVel = -yVel*bounceFactor; } else yVel = 0;
140 if (fabs(xVel) < 0.1) xVel = 0;
141 else if (fabs(xVel) != 0) xVel *= frictionFactor;
142 if (fabs(yVel) < 1) {
144 if (!isCollisionBottom(1)) flty += 1;
150 if (!colRight) fltx += 1;
152 } else if (colRight) {
157 if (isCollisionTop(1)) {
158 if (yVel < 0) yVel = -yVel*0.8; else flty += 1;
161 //!depth = (global.hasSpectacles ? 0 : 101); //???
163 if (isCollisionInRect(ix-3, iy-3, 7, 7, &level.cbCollisionLava)) {
172 if (isCollisionAtPoint(ix, iy-5, &level.cbCollisionLava)) {
178 if (!isInstanceAlive || spectral) return;
180 if (fabs(xVel) > 3 || fabs(yVel) > 3) {
182 auto plr = level.player;
183 if (plr.isRectCollision(ix+enemyColX, iy+enemyColY, enemyColW, enemyColH)) {
184 doPlayerColAction(plr);
188 level.forEachObjectInRect(ix-2, iy-2, 5, 5, &doObjectColAction);
193 if (fabs(yVel) > 2 || fabs(xVel) > 2) {
194 setSprite('sDiceRoll');
195 value = global.randOther(1, 6);
197 case RollState.Finished:
199 if (level.player.bet > 0) level.scrShopkeeperAnger(GameLevel::SCAnger.CrapsCheated);
202 rollState = RollState.Rolling;
205 } else if (yVel == 0 && fabs(xVel <= 2) && isCollisionBottom(1)) {
208 case RollState.Rolling:
209 rollState = (isInsideCrapsShop() ? RollState.Finished : RollState.Failed);
210 if (rollState == RollState.Finished) level.player.onDieRolled(self);
212 case RollState.Finished:
213 case RollState.Failed:
216 default: rollState = RollState.None; break;
221 case 1: setSprite('sDice1'); break;
222 case 2: setSprite('sDice2'); break;
223 case 3: setSprite('sDice3'); break;
224 case 4: setSprite('sDice4'); break;
225 case 5: setSprite('sDice5'); break;
226 default: setSprite('sDice6'); break;
229 canPickUp = (rollState != RollState.Rolling);
236 desc2 = "A six-sided die. The storeowner talks to it every night before he goes to sleep.";
237 setCollisionBounds(-6, 0, 6, 8);
244 rollState = RollState.None;
245 bloodless = true; // just in case, lol
246 canHitEnemies = true;