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 MapTileArrowTrap : MapTile abstract;
23 //const name soundName = 'sndArrowTrap';
27 int recheckTimer = -1;
28 int checkerX, checkerY, checkerW; // checkerW == 0 means "setup it now"
29 const int checkerH = 16;
30 const int checkerDuckOfs = -2;
35 override bool smashMe () {
36 auto res = ::smashMe();
37 if (res && !isInstanceAlive && !fired) {
38 level.MakeMapObject(ix+8, iy+8, 'oArrow');
44 override void setupTile () {
45 if (global.cityOfGold == 1) {
51 if (global.cityOfGold != 1)
53 if (smashed) scrSprayRubble(3, sRubbleTan, sRubbleTanSmall);
54 scrDropRubble(3, sRubbleTan, sRubbleTanSmall);
55 if (fired == 0) instance_create(x+8, y+8, projectile);
59 for (i = 0; i < 3; i += 1)
61 gold = instance_create(x+8+rand(0,4)-rand(0,4), y+8+rand(0,4)-rand(0,4), oGoldChunk);
62 gold.xVel = rand(0,3) - rand(0,3);
63 gold.yVel = rand(2,4) * 1;
65 gold = instance_create(x+8+rand(0,4)-rand(0,4), y+8+rand(0,4)-rand(0,4), oGoldNugget);
66 gold.xVel = rand(0,3) - rand(0,3);
67 gold.yVel = rand(2,4) * 1;
68 if (fired == 0) instance_create(x+8, y+8, projectile);
75 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
76 ::drawWithOfs(xpos, ypos, scale, currFrameDelta);
78 auto oclr = GLVideo.color;
79 GLVideo.color = 0x7f_00_ff_00;
80 GLVideo.fillRect(checkerX*scale-xpos, checkerY*scale-ypos, checkerW*scale, checkerH*scale);
87 transient MapTile nearestSolid;
89 final void setupChecker () {
90 // calculate how far to motion detect
91 //writeln("setting up sensor area for arrow trap at (", ix/16, ",", iy/16, ")");
93 if (dir == Dir.Right) {
94 level.checkTilesInRect(ix+width, iy, range, height, delegate bool (MapTile t) {
95 if (t == self || !t.solid || !t.visible) return false;
96 if (!nearestSolid || nearestSolid.ix-ix > t.ix-ix) nearestSolid = t;
101 checkerW = max(1, (nearestSolid ? nearestSolid.ix-checkerX : range));
103 level.checkTilesInRect(ix-range, iy, range, height, delegate bool (MapTile t) {
104 if (t == self || !t.solid || !t.visible) return false;
105 if (!nearestSolid || ix-nearestSolid.x1 > ix-t.x1) nearestSolid = t;
108 checkerX = (nearestSolid ? nearestSolid.x1+1 : ix-range);
110 checkerW = max(1, ix-checkerX);
115 final void fireProjectile () {
118 playSound('sndArrowTrap');
119 auto arrow = level.MakeMapObject(ix+(dir == Dir.Left ? -2-3 : 18+3), iy+4, 'oArrow');
121 float xvel = fclamp(vel, 0, 24);
122 arrow.xVel = (dir == Dir.Left ? -xvel : xvel);
124 fireTimer = global.randOther(100, 200);
129 override void thinkFrame () {
131 if (fireTimer < 0) fireTimer = global.randOther(100, 200);
132 if (--fireTimer <= 0) {
139 if (fired) return; // come along, nothing to see here
141 if (level.someTilesRemoved && recheckTimer < 0) recheckTimer = global.randOther(4, 8);
142 if (recheckTimer >= 0) --recheckTimer;
144 if (checkerW == 0 || recheckTimer == 0) setupChecker();
146 auto plr = level.player;
148 if (plr.imageFrame > 6 && (plr.spriteLName == 'sDuckToHangL' || plr.spriteLName == 'sDamselDtHL' || plr.spriteLName == 'sTunnelDtHL')) {
150 writeln("*** DTH! xVel=", plr.xVel, "; yVel=", plr.yVel, "; ifrm=", trunc(plr.imageFrame));
151 writeln("me:(", checkerX, ",", checkerY, ")-(", checkerX+15, ",", checkerY+15, "); plr:(", plr.x0, ",", plr.y0, ")-(", plr.x1, ",", plr.y1, ")");
152 if (plr.isRectCollision(checkerX, checkerY, checkerW, checkerH)) {
153 writeln(" HIT!!!!!!!!!!");
156 // -2, due to ducking hitbox
157 if (plr.isRectCollision(checkerX, checkerY+checkerDuckOfs, checkerW, checkerH)) {
164 if (plr.xVel != 0 || plr.yVel != 0) {
165 if (plr.isRectCollision(checkerX, checkerY, checkerW, checkerH)) {
166 //writeln("xVel=", plr.xVel, "; yVel=", plr.yVel);
174 auto o = EnemySpiderHang(xspider);
175 //writeln("o: ", GetClassName(o.Class), "; xVel=", o.xVel, "; yVel=", o.yVel, "; status=", o.status);
176 writeln("o:(", o.x0, ",", o.y0, ")-(", o.x1, ",", o.y1, "); checker:(", checkerX, ",", checkerY, ")-(", checkerX+checkerW-1, ",", checkerY+checkerH-1, ")");
180 MapEntity hitDetected = level.forEachObjectInRect(checkerX, checkerY, checkerW, checkerH, delegate bool (MapObject o) {
185 if (o.spectral || !o.visible) return false;
186 if (global.config.bomsDontSetArrowTraps && (o isa ItemBomb || o isa ItemRopeThrow)) {
187 // armed == true for rope means "it is launched by a player"
188 if (!o.armed) return false;
190 if (o isa MapEnemy || o isa MapItem) {
191 //if (/*!xspider &&*/ o isa EnemySpiderHang) xspider = o;
192 //writeln("o: ", GetClassName(o.Class), "; xVel=", o.xVel, "; yVel=", o.yVel, "; status=", o.status);
193 if (o.xVel != 0 || o.yVel != 0) {
194 return o.isRectCollision(checkerX, checkerY, checkerW, checkerH);
201 // moveable block or rope should trigger it too
202 hitDetected = level.checkTilesInRect(checkerX, checkerY, checkerW, checkerH, delegate bool (MapTile o) {
203 if (o.visible && !o.spectral) {
204 if ((o.solid && (o.xVel != 0 || o.yVel != 0)) || o isa MapTileRope) {
205 return o.isRectCollision(checkerX, checkerY, checkerW, checkerH);
213 writeln("arrow trap detected ", GetClassName(hitDetected.Class), "(", hitDetected.objType, ":", hitDetected.objName, ")");
222 toSpecialGrid = true; // it need to think, so...
226 // ////////////////////////////////////////////////////////////////////////// //
227 class MapTileArrowTrapLeft['oArrowTrapLeft'] : MapTileArrowTrap;
230 override void setupTile () {
232 objType = 'oArrowTrap';
233 spriteName = (global.cityOfGold == 1 ? 'sArrowTrapGoldLeft' : 'sArrowTrapLeft');
238 // ////////////////////////////////////////////////////////////////////////// //
239 class MapTileArrowTrapRight['oArrowTrapRight'] : MapTileArrowTrap;
242 override void setupTile () {
243 objType = 'oArrowTrap';
244 spriteName = (global.cityOfGold == 1 ? 'sArrowTrapGoldRight' : 'sArrowTrapRight');
249 // ////////////////////////////////////////////////////////////////////////// //
250 class MapTileArrowTrapLeftLit['oArrowTrapLeftLit'] : MapTileArrowTrapLeft;
257 // ////////////////////////////////////////////////////////////////////////// //
258 class MapTileArrowTrapRightLit['oArrowTrapRightLit'] : MapTileArrowTrapRight;
265 // ////////////////////////////////////////////////////////////////////////// //
266 class MapTileArrowRepeaterLeft['oArrowRepeaterL'] : MapTileArrowTrapLeft;
269 override void setupTile () {
274 desc = "Arrow Repeater";
275 desc2 = "This trap is armed with a large supply of arrows that it shoots in a slow steam.";
280 // ////////////////////////////////////////////////////////////////////////// //
281 class MapTileArrowRepeaterRight['oArrowRepeaterR'] : MapTileArrowTrapRight;
284 override void setupTile () {
290 desc = "Arrow Repeater";
291 desc2 = "This trap is armed with a large supply of arrows that it shoots in a slow steam.";