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 ObjGhost['oGhost'] : MapObject;
22 override bool initialize () {
23 if (!::initialize()) return false;
24 //setSprite('sGhostLeft', 'sGhostRight');
25 setSprite('sGhostRight');
26 playSound('sndGhost');
31 override void getLightOffset (out int lxofs, out int lyofs) {
37 override void onAnimationLooped () {
38 auto spr = getSprite();
39 if (spr.Name == 'sGhostTurnRight') setSprite('sGhostRight');
40 else if (spr.Name == 'sGhostTurnLeft') setSprite('sGhostLeft');
41 else if (spr.Name == 'sGhostDisappear') instanceRemove();
45 // for dead players too
46 // first, the code will call `onObjectTouched()` for player
47 // if it returned `false`, the code will call this handler
48 // note that player's handler is called *after* its frame thinker,
49 // but object handler is called *before* frame thinker for the object
50 // return `true` to skip thinker on this frame
51 override bool onTouchedByPlayer (PlayerPawn plr) {
52 if (plr.dead) return false;
53 if (plr.invincible != 0) return false;
55 //!if (isRealLevel()) global.enemyDeaths[23] += 1;
56 foreach (; 0..3) level.MakeMapObject(plr.ix, plr.iy, 'oBone');
57 auto skull = level.MakeMapObject(plr.ix, plr.iy-2, 'oSkull');
59 if (skull isa ItemThrowable) ItemThrowable(skull).breaksOnCollision = false;
60 skull.yVel = -global.randOther(1, 3);
61 skull.xVel = global.randOther(0, 3)-global.randOther(0, 3);
66 plr.invincible = 9999;
71 if (global.plife <= 0 /*and isRealLevel()*/) level.addDeath(objName);
73 plr.removeActivatedPlayerWeapon();
74 plr.scrDropItem(LostCause.Throw, (plr.dir == Dir.Left ? -2 : 2), -4);
76 auto it = plr.holdItem;
79 it.xVel = (plr.dir == Dir.Left ? -2 : 2);
86 setSprite('sGhostDisappear');
87 playSound('sndGhost');
89 // she should disappear after she kills you
96 transient array!MapObject gemsToConvert;
99 override void thinkFrame () {
101 if (countsAsKill) level.addKill(objName);
106 if (status == IDLE) {
108 } else if (status == ATTACK) {
109 //auto dist = pointDistance(ix+8, iy+8, level.player.ix, level.player.iy);
110 float dir = pointDirection(ix+8, iy+8, level.player.ix, level.player.iy);
113 auto spr = getSprite();
114 if (level.player.ix < ix+8) {
115 if (spr.Name == 'sGhostRight') setSprite('sGhostTurnLeft');
117 if (spr.Name == 'sGhostLeft') setSprite('sGhostTurnRight');
122 gemsToConvert.clear();
123 level.isObjectInRect(x0, y0, width, height, delegate bool (MapObject o) {
124 if (o isa ItemBigGem && o.isInstanceAlive) gemsToConvert[$] = o;
128 foreach (MapObject g; gemsToConvert) {
129 /*auto diamond =*/ level.MakeMapObject(g.ix, g.iy, 'oDiamond');
132 gemsToConvert.clear();
134 // convert gems inside tiles
135 level.checkTilesInRect(x0, y0, width, height, delegate bool (MapTile t) {
136 t.convertGemObjectToDiamond(self);
146 desc2 = "These apparitions haunt the ruins of ancient civilizations and burial grounds, hunting down intruders and ending their life with a single touch. There is no weapon that can harm their insubstantial bodies.";
147 setCollisionBounds(4, 0, 12, 16);
161 imageAlpha = 0.6; //255-int(0.6*255);
175 disableMirror = true;
177 canLiveOutsideOfLevel = true;