fixed alot of warnings, and some bugs, so it can be compiled with the current vccrun
[k8vacspelynky.git] / mapent / MapObject.vc
blob4d73ca6ca35be3bbcd9a6bf1dc8c1cc1b6b052f7
1 /**********************************************************************************
2  * Copyright (c) 2008, 2009 Derek Yu and Mossmouth, LLC
3  * Copyright (c) 2010, Moloch
4  * Copyright (c) 2018, Ketmar Dark
5  *
6  * This file is part of Spelunky.
7  *
8  * You can redistribute and/or modify Spelunky, including its source code, under
9  * the terms of the Spelunky User License.
10  *
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.
13  *
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/>
17  *
18  **********************************************************************************/
19 // this is self-movable map object
20 class MapObject : MapEntity;
22 enum {
23   // olmec
24   START2 = -2,
25   START1 = -1,
27   IDLE = 0,
28   WALK,
29   ATTACK,
30   HANG, // for bats, spiders and monkeys
31   BOUNCE,
32   RECOVER,
33   DROWNED,
34   CRAWL,
35   SQUIRT,
37   // constant states that the platform character may be
38   STANDING = 10,
39   RUNNING,
40   DUCKING,
41   LOOKING_UP,
42   CLIMBING,
43   JUMPING,
44   FALLING,
45   DYING,
46   LEFT,
47   RIGHT,
48   ON_GROUND,
49   IN_AIR,
50   ON_LADDER,
51   HANGING,
52   DUCKTOHANG,
54   // damsel states
55   RUN,
56   THROWN,
57   YELL,
58   EXIT,
59   SLAVE,
60   KISS,
62   // shopkeeper
63   THROW,
64   PATROL,
65   FOLLOW,
67   // vampire
68   FLY,
70   // green frog
71   SPIT1,
72   SPIT2,
73   SPIT3,
75   // piranha
76   PAUSE,
77   ATTACK_ENEMY,
79   // megamouth, tomb lord
80   TURN,
82   // player in transition level
83   STOPPED, // damsel
84   STOPPED_TUNNEL, // tunnel man
86   // monkey
87   CLIMB,
88   GRAB,
90   // spring trap
91   SPRUNG,
93   // ufo
94   SEARCH,
95   DESTROY,
96   BLAST,
98   // alien eject
99   EJECT,
100   DEPLOY,
101   FLOAT,
103   // olmec
104   PREPARE,
105   SLAM,
106   CREATE,
107   DROWNING,
109   // mantrap
110   SLEEPY = 96,
111   EATING = 97,
112   STUNNED = 98,
113   DEAD = 99,
115   // look
116   UP = 101,
117   DOWN = 102,
121 bool hiddenTreasure; // true: cannot be seen without spectacles
122 MapTile ownerTile;
125 transient SpriteImage spriteL;
126 transient SpriteImage spriteR; // can be empty
127 name spriteLName, spriteRName;
128 bool negateMirrorXOfs;
129 bool disableMirror;
130 bool carryPlayer; // olmec
132 int bounceCount;
135 MapObject mHeldBy;
136 MapObject mHoldItem;
137 //MapObject alreadyHeld;
138 int savedDepth;
139 int holdDepth = 1;
140 bool activeWhenHeld = false;
141 bool spectralWhenHeld = true;
143 bool sellingToShopAllowed = false;
144 bool fixedPrice = false;
145 bool sellOfferDone = false; // `true`, if selling was offered, next `PAY` will sell it
147 string shopDesc;
150 final MapObject heldBy { get { return mHeldBy; } }
153 #ifndef STANDALONE_MAP_ENTITY
154 // called after `heldBy` becomes `none` from something
155 // only for alive instances
156 void onHoldReset (MapObject oldholder) {
157   active = true;
158   visible = true;
159   myGrav = myGravNorm; // stakes will reset gravity
160   spectral = false; // just in case
161   imageAngle = 0;
162   // holding depth
163   depth = savedDepth;
164   sellOfferDone = false;
165   bounceCount = 0;
166   //writeln("HOLD RESET FOR '", GetClassName(Class), "'");
167   if (mHeldBy) FatalError("WTF?!");
171 // called after `heldBy` becomes something `none`
172 // only for alive instances
173 void onHoldSet (MapObject newholder) {
174   //active = (self isa MapEnemy || armed);
175   active = activeWhenHeld;
176   spectral = spectralWhenHeld;
177   visible = true;
178   imageAngle = 0;
179   // holding depth
180   savedDepth = depth;
181   depth = 1;
182   sellOfferDone = false;
183   //writeln("HOLD SET FOR '", GetClassName(Class), "' (activeWhenHeld=", activeWhenHeld, ")");
184   //bounceCount = 0;
185   if (!mHeldBy) FatalError("WTF?!");
189 // called after `mHoldItem` becomes `none` from something
190 // only for alive instances
191 // note that `olditem` can be dead instance
192 // called after `onHoldReset()`
193 void onHoldItemReset (MapObject olditem) {
194   if (olditem) olditem.makeSafe();
198 // called after `mHoldItem` becomes something `none`
199 // only for alive instances
200 // called after `onHoldSet()`
201 // note that `newitem` can be dead instance in rare cases
202 void onHoldItemSet (MapObject newitem) {
203   if (newitem) newitem.makeSafe();
207 override void makeSafe () {
208   ::makeSafe();
209   safe = true;
210   alarmDisarmSafe = 10;
214 MapObject holdItem {
215   get mHoldItem;
216   set(it) {
217     auto hi = mHoldItem;
218     // just in case: unhold dead instance
219     if (hi && !hi.isInstanceAlive) {
220       hi.mHeldBy = none;
221       mHoldItem = none;
222       if (isInstanceAlive) onHoldItemReset(hi);
223       hi = none;
224     }
225     // just in case: don't hold dead instance
226     if (it && !it.isInstanceAlive) {
227       it.mHeldBy = none;
228       it = none;
229     }
230     // already holding it?
231     if (it == hi) {
232       // just in case
233       if (it && it.mHeldBy != self) FatalError("something is VERY wrong with carrying item management");
234       return;
235     }
236     // unheld current item
237     if (hi) {
238       hi.mHeldBy = none;
239       mHoldItem = none;
240       fixHoldCoords();
241       if (hi.isInstanceAlive) hi.onHoldReset(self);
242       if (isInstanceAlive) onHoldItemReset(hi);
243     }
244     // and take new one
245     mHoldItem = it;
246     if (it) {
247       it.mHeldBy = self;
248       fixHoldCoords();
249       if (it.isInstanceAlive) it.onHoldSet(self);
250       if (isInstanceAlive) onHoldItemSet(hi);
251     }
252   }
254 #endif
256 protected int hitboxX, hitboxY, hitboxW, hitboxH;
258 override int x0 () { return roundi(fltx)+hitboxX; }
259 override int y0 () { return roundi(flty)+hitboxY; }
260 override int width () { return hitboxW; }
261 override int height () { return hitboxH; }
264 float xAcc, yAcc;
265 float xDelta, yDelta;
266 float myGravNorm = 0.6;
267 float myGravWater = 0.2;
268 float bounceFactor = 0.5;
269 float frictionFactor = 0.3;
271 float xVelLimit = 16; // limits the xVel: default 15
272 float yVelLimit = 10; // limits the yVel
273 float xAccLimit = 9;  // limits the xAcc
274 float yAccLimit = 6;  // limits the yAcc
275 float runAcc = 3;     // the running acceleration
277 Dir dir = Dir.Left;
280 // object flags
281 int invincible; // counter
282 int alarmNudge;
283 name shopType; // for items: in which shop it is placed? for player: last visited shop
285 bool persist = true;
286 bool bounced;
287 bool stunned;
288 bool dead;
289 //bool cleanDeath;
290 bool flying;
291 bool heavy;
292 bool bounce;
293 bool bounceTop;
294 bool collectible;
296 bool stuck;
297 bool nudged;
299 bool canBeHitByBullet;
300 bool canBeNudged;
302 bool inDiceHouse;
303 bool forSale;
304 //bool damselDropped;
305 bool isTreasure;
306 bool canCollect;
308 // items
309 //bool held;
310 bool armed;
311 bool trigger;
312 bool safe;
313 bool sticky;
314 bool canPickUp;
315 bool cannotBeCarriedOnNextLevel;
316 bool allowWaterProcessing = true;
318 int alarmDisarmSafe;
319 int cost;
320 int value;
321 int resaleValue;
322 //string carries = "";
323 //string holds = "";
324 int favor = 1;
325 int sacCount = 20;
327 // enemy and player
328 int hp;
329 int status;
330 int counter; // usually for stun
332 bool bloodless;
333 bool swimming;
334 bool inWeb;
335 bool countsAsKill = true; // sometimes it's not the player's fault!
336 bool removeCorpse; // only applies to enemies that have corpses (Caveman, Yeti, etc.)
337 bool meGoldMonkey;
338 bool dying;
339 bool cursed;
340 bool wasCollected;
342 int bloodLeft = 4;
343 int bloodOfsX = int.min, bloodOfsY = int.min; // this means "center"
344 int burning;
345 int sightCounter;
346 int stunTime = 200;
347 int damage = 1; // damage amount caused to player/enemy on touch
348 int burnTimer;
350 int deathTimer = 200; // how many steps after death until corpse is removed
352 int fallTimer;
353 int stunTimer;
354 int wallHurt;
355 //MapObject thrownBy; // "Yeti", "Hawkman", or "Shopkeeper" for stat tracking deaths by being thrown
356 name thrownBy; // name, 'cause this is the only thing we are interested in, and the object can die
357 int pushTimer;
358 int whoaTimer;
359 const int whoaTimerMax = 30;
360 int distToNearestLightSource = int.max;
363 // ////////////////////////////////////////////////////////////////////////// //
364 #ifndef STANDALONE_MAP_ENTITY
365 override void onLoaded () {
366   ::onLoaded();
367   if (spriteLName) spriteL = level.sprStore[spriteLName];
368   if (spriteRName) spriteR = level.sprStore[spriteRName];
372 void fixHoldCoords () {
376 final void forceFixHoldCoords (MapObject holder) {
377   if (!holder) return;
378   if (mHeldBy == holder) return;
379   auto oldHolder = mHeldBy;
380   auto oldHolderItem = holder.mHoldItem;
381   mHeldBy = holder;
382   holder.mHoldItem = self;
383   fixHoldCoords();
384   holder.mHoldItem = oldHolderItem;
385   mHeldBy = oldHolder;
387 #endif
390 // ////////////////////////////////////////////////////////////////////////// //
391 override void onDestroy () {
392 #ifndef STANDALONE_MAP_ENTITY
393   if (heldBy) {
394     heldBy.holdItem = none;
395     mHeldBy = none;
396   }
397 #endif
401 #ifndef STANDALONE_MAP_ENTITY
402 // ////////////////////////////////////////////////////////////////////////// //
403 override void onOutOfLevel () {
404   if (canLiveOutsideOfLevel || heldBy) return;
405   if (yVel < -0.01) return; // it is flying up
406   // remove it only if it fallen down
407   if (y0 > level.tilesHeight*16+16) instanceRemove();
411 // ////////////////////////////////////////////////////////////////////////// //
412 // does only sprite collision, ignoring hitbox
413 private transient bool delegate (MapObject o) privNoDimsObjCheckerDG;
414 private transient bool delegate (MapTile t) privNoDimsTileCheckerDG;
416 final MapObject collideObjectsNoDims (optional scope bool delegate (MapObject o) dg) {
417   int x0, y0, x1, y1;
418   /*auto spf =*/ getSpriteFrame(default, x0, y0, x1, y1);
419   if (x1 <= x0 || y1 <= y0) return none;
421   bool delegate (MapObject o) olddg = privNoDimsObjCheckerDG;
422   privNoDimsObjCheckerDG = dg;
423   auto spc = spectral;
424   spectral = true;
425   auto obj = level.isObjectInRect(ix+x0, iy+y0, x1-x0, y1-y0, delegate bool (MapObject o) {
426     if (o.spectral) return false;
427     if (!o.active) return false;
428     //if (o isa MapEnemy && o.dead) return false;
429     if (!o.collidesWith(self, ignoreDims:true)) return false;
430     if (!privNoDimsObjCheckerDG) return true;
431     return privNoDimsObjCheckerDG(o);
432   });
433   spectral = spc;
434   privNoDimsObjCheckerDG = olddg;
436   return obj;
439 final MapTile collideTilesNoDims (optional scope bool delegate (MapTile o) dg) {
440   int x0, y0, x1, y1;
441   /*auto spf =*/ getSpriteFrame(default, x0, y0, x1, y1);
442   if (x1 <= x0 || y1 <= y0) return none;
444   bool delegate (MapTile t) olddg = privNoDimsTileCheckerDG;
445   privNoDimsTileCheckerDG = dg;
446   auto spc = spectral;
447   spectral = true;
448   auto obj = level.checkTilesInRect(ix+x0, iy+y0, x1-x0, y1-y0, delegate bool (MapTile t) {
449     if (t.spectral) return false;
450     if (!t.collidesWith(self, ignoreDims:true)) return false;
451     if (!privNoDimsTileCheckerDG) return t.solid; // default
452     return privNoDimsTileCheckerDG(t);
453   });
454   spectral = spc;
455   privNoDimsTileCheckerDG = olddg;
457   return obj;
461 // ////////////////////////////////////////////////////////////////////////// //
462 // create cost
463 void generateCost () {
464   if (cost <= 0) cost = 100;
465   if (global.currLevel > 2) cost += (cost/100)*10*(global.currLevel-2);
469 override bool initialize () {
470   if (!::initialize()) return false;
471   if (sellingToShopAllowed) {
472     if (cost > 0 && !fixedPrice) generateCost();
473   }
474   return true;
477 // ////////////////////////////////////////////////////////////////////////// //
478 final void scrCreateBlood (int x, int y, int amount) {
479   //if (bloodless) return;
480   if (amount <= 0) return;
481   foreach (; 0..amount) {
482     auto blood = level.MakeMapObject(x, y, 'oBlood');
483     if (blood && xVel != 0) {
484       if ((xVel < 0 && blood.xVel > 0) || (xVel > 0 && blood.xVel < 0)) blood.xVel = -blood.xVel;
485       blood.xVel += fclamp(xVel, -1, 1);
486     }
487   }
491 final void scrCreateFlame (int x, int y, int amount) {
492   foreach (; 0..amount) {
493     level.MakeMapObject(x, y, 'oFlame');
494   }
498 final void scrCreateBloblets (int px, int py, int count) {
499   foreach (; 0..count) level.MakeMapObject(px, py, 'oBloblet');
501 #endif
504 // ////////////////////////////////////////////////////////////////////////// //
505 final void setCollisionBounds (int hx0, int hy0, int hx1, int hy1) {
506   hitboxX = hx0;
507   hitboxY = hy0;
508   hitboxW = max(0, hx1-hx0);
509   hitboxH = max(0, hy1-hy0);
513 final void setCollisionBoundsFromFrame () {
514   int fx0, fy0, fx1, fy1;
515   if (getSpriteFrame(default, out fx0, out fy0, out fx1, out fy1)) {
516     setCollisionBounds(fx0, fy0, fx1, fy1);
517   }
521 // ////////////////////////////////////////////////////////////////////////// //
522 void spillBlood (optional int amount, optional bool forced) {
523   if (!bloodless && (forced || bloodLeft > 0)) {
524     if (!specified_amount) amount = 3;
525     if (amount <= 0) return;
526     int x = (bloodOfsY != int.min ? ix+bloodOfsX : xCenter);
527     int y = (bloodOfsX != int.min ? iy+bloodOfsY : yCenter);
528     scrCreateBlood(x, y, amount);
529     if (hp <= 0) bloodLeft -= 1;
530   }
534 // ////////////////////////////////////////////////////////////////////////// //
535 bool checkAndPerformSacrifice (optional out bool onTheAltar) {
536   onTheAltar = false;
537   if (!heldBy && fabs(xVel) < 0.001 && fabs(yVel) < 0.001) {
538     auto myAltar = isCollisionAtPoint(ix+8, iy+16, &level.cbCollisionSacAltar);
539     onTheAltar = !!myAltar;
540     if (myAltar && canBeSacrificed(myAltar)) {
541       if (sacCount > 0) {
542         --sacCount;
543       } else {
544         sacCount = default.sacCount;
545         if (onSacrificed(myAltar) || !isInstanceAlive) return true;
546       }
547     } else {
548       sacCount = default.sacCount;
549     }
550   } else {
551     sacCount = default.sacCount;
552   }
553   return false;
557 // ////////////////////////////////////////////////////////////////////////// //
558 // virtual
559 void nudgeIt (int nx, int ny, optional bool forced) {
563 // virtual
564 void onBulletHit (ObjBullet bullet) {
568 // virtual
569 // called when player is trying to pick up something
570 // return `true` if this entity can be picekd up
571 // this is used in search loop, it is called before `onTryPickup()`
572 // DON'T do any actions here, this should be a pure checker!
573 // not called for inactive or spectral objects
574 bool onCanBePickedUp (PlayerPawn plr) { return false; }
577 // virtual
578 // return `true` to stop player from holding it
579 bool onTryPickup (PlayerPawn plr) {
580   return false;
584 // virtual
585 // various side effects
586 // called only if object was succesfully put into player hands
587 void onPickedUp (PlayerPawn plr) {
588   if (!wasCollected) { wasCollected = true; level.addCollect(objName); }
592 // virtual
593 // return `true` to stop player from throwing it
594 bool onTryUseItem (PlayerPawn plr) {
595   return false;
599 // virtual
600 void onBeforeThrowBy (PlayerPawn plr) {
604 #ifndef STANDALONE_MAP_ENTITY
605 // return `false` to prevent
606 // owner is usually a player
607 override bool onLostAsHeldItem (MapObject owner, LostCause cause, optional float xvel, optional float yvel) {
608   resaleValue = 0;
609   if (cause == LostCause.Whoa) {
610     xVel = (owner.dir == Dir.Left ? -2 : 2);
611     //!if (type == "Damsel") playSound(global.sndDamsel);
612     //!if (type == "Bow" and bowArmed) scrFireBow();
613     //!if (type == "Block Item") with (oBlockPreview) instance_destroy(); // YASM 1.8.1
614     /+!
615     if (type == pickupItemType || (type == "Shotgun" and pickupItemType == "Boomstick")) {
616       holdItem = 0;
617       pickupItemType = "";
618     } else {
619       scrHoldItem(pickupItemType);
620     }
621     +/
622     if (objName == 'GoldIdol') shiftY(-8); //FIXME: move to the respective class
623     return true;
624   } else if (cause == LostCause.Drop) {
625     visible = true;
626     resaleValue = 0;
627     //!if (bowArmed) scrFireBow();
628     /*
629     if (pickupItemType != type) {
630       scrHoldItem(pickupItemType);
631     } else {
632       if (type == "Block Item") { with (oBlockPreview) instance_destroy(); }
633       holdItem = 0;
634       pickupItemType = "";
635     }
636     */
637   } else if (cause == LostCause.Dead || cause == LostCause.Stunned) {
638     visible = true;
640     /*!
641     if (type == "Arrow") {
642       safe = true;
643       alarm[2] = 30; // prevent held arrow from hurting player as it flies out of hands
644     }
645     */
647     /+!
648     if (type == pickupItemType || (type == "Shotgun" and pickupItemType == "Boomstick")) {
649       holdItem = 0;
650       pickupItemType = "";
651     } else {
652       scrHoldItem(pickupItemType);
653     }
654     +/
655   }
656   if (specified_xvel) xVel = xvel;
657   if (specified_yvel) yVel = yvel;
658   if (objName == 'GoldIdol') shiftY(-8); //FIXME: move to the respective class
659   return true;
661 #endif
664 // ////////////////////////////////////////////////////////////////////////// //
665 override SpriteImage getSprite (optional out bool doMirror) {
666   doMirror = false;
667   if (spriteL || spriteR) {
668     SpriteImage spr = (dir == Dir.Left ? spriteL : spriteR);
669     if (!spr) {
670       spr = (dir != Dir.Left ? spriteL : spriteR);
671       if (spr && !disableMirror) doMirror = true;
672     }
673     return spr;
674   }
675   return none;
679 // x1, y1: exclusive
680 override SpriteFrame getSpriteFrame (optional out bool doMirror, optional out int x0, optional out int y0, optional out int x1, optional out int y1) {
681   auto spr = getSprite(doMirror!optional);
682   if (!spr || spr.frames.length == 0) return none;
683   auto spf = spr.frames[trunci(imageFrame)%spr.frames.length];
684   if (!spf) return none;
685   if (specified_y0 || specified_y1) {
686     y0 = -spf.yofs;
687     y1 = y0+spf.tex.height;
688   }
689   if (specified_x0 || specified_x1) {
690     if (!doMirror || !negateMirrorXOfs) {
691       x0 = -spf.xofs;
692       x1 = x0+spf.tex.width;
693     } else {
694       x1 = spf.xofs;
695       x0 = x1-spf.tex.width;
696     }
697   }
698   return spf;
702 override void clearSprite () {
703   spriteL = none;
704   spriteR = none;
705   spriteLName = '';
706   spriteRName = '';
707   imageFrame = 0;
708   imageLoopCount = 0;
712 #ifndef STANDALONE_MAP_ENTITY
713 override void setSprite (name sprNameL, optional name sprNameR) {
714   if (!sprNameL && !sprNameR) {
715     clearSprite();
716     return;
717   }
719   bool resetFrames = false;
721   if (sprNameL && sprNameR) {
722     if (!spriteL || spriteL.Name != sprNameL) {
723       spriteLName = sprNameL;
724       spriteL = level.sprStore[sprNameL];
725       resetFrames = (dir == Dir.Left);
726     }
727     if (!spriteR || spriteR.Name != sprNameR) {
728       spriteRName = sprNameR;
729       spriteR = level.sprStore[sprNameR];
730       resetFrames = (dir == Dir.Right);
731     }
732   } else if (sprNameL) {
733     if (!spriteL || spriteL.Name != sprNameL) {
734       spriteLName = sprNameL;
735       spriteL = level.sprStore[sprNameL];
736       resetFrames = (dir == Dir.Left);
737     }
738     if (spriteR) {
739       spriteRName = '';
740       spriteR = none;
741       resetFrames = (dir == Dir.Right);
742     }
743   } else if (sprNameR) {
744     if (!spriteR || spriteR.Name != sprNameR) {
745       spriteRName = sprNameR;
746       spriteR = level.sprStore[sprNameR];
747       resetFrames = (dir == Dir.Right);
748     }
749     if (spriteL) {
750       spriteLName = '';
751       spriteL = none;
752       resetFrames = (dir == Dir.Left);
753     }
754   }
756   if (resetFrames) {
757     imageFrame = 0;
758     imageLoopCount = 0;
759   }
761 #endif
764 // ////////////////////////////////////////////////////////////////////////// //
765 #ifndef STANDALONE_MAP_ENTITY
766 final void basicPhysicsStep () {
767   moveRel(xVel, yVel);
768   if (!flying) {
769     yVel += myGrav;
770     if (yVel > yVelLimit) yVel = yVelLimit;
771     if (!canLiveOutsideOfLevel && !heldBy && isOutsideOfLevel()) {
772       // oops, fallen out of level...
773       onOutOfLevel();
774     }
775   }
779 override void thinkFrame () {
780   if (!heldBy) basicPhysicsStep();
782 #endif
785 override void processAlarms () {
786   ::processAlarms();
787   // nudge
788   if (alarmNudge > 0) {
789     if (--alarmNudge == 0) nudged = false;
790   }
791   // safe
792   if (alarmDisarmSafe > 0) {
793     //writeln("DSF: ", alarmDisarmSafe);
794     if (--alarmDisarmSafe == 0) {
795       //writeln("DSF: ", alarmDisarmSafe);
796       safe = false;
797     }
798   }
802 // ////////////////////////////////////////////////////////////////////////// //
803 override void drawWithOfs (int xpos, int ypos, int scale, float currFrameDelta) {
804   int xi, yi;
805   getInterpCoords(currFrameDelta, scale, out xi, out yi);
807   bool doMirror;
808   int fx0, fy0, fx1, fy1;
809   auto spf = getSpriteFrame(out doMirror, out fx0, out fy0, out fx1, out fy1);
810   if (!spf) return;
812   auto oclr = GLVideo.color;
813   GLVideo.color = oclr|(trunci(fclamp(255.0-255*imageAlpha, 0.0, 255.0))<<24);
815   fx0 = xi+fx0*scale-xpos;
816   fy0 = yi+fy0*scale-ypos;
817   fx1 = xi+fx1*scale-xpos;
818   fy1 = yi+fy1*scale-ypos;
819   if (!doMirror) {
820     spf.tex.blitExt(fx0, fy0, fx1, fy1, 0, 0, spf.width, spf.height, angle:imageAngle);
821   } else {
822     spf.tex.blitExt(fx0, fy0, fx1, fy1, spf.width, 0, 0, spf.height, angle:imageAngle);
823   }
824   GLVideo.color = oclr;
826 #ifndef STANDALONE_MAP_ENTITY
827   if (false) {
828     oclr = GLVideo.color;
829     GLVideo.color = 0xff_ff_00;
830     GLVideo.drawRect(x0*scale-xpos, y0*scale-ypos, width*scale, height*scale);
831     GLVideo.color = 0x00_ff_00;
832     GLVideo.drawRect(ix*scale-xpos, iy*scale-ypos, 2, 2);
834     if (isCollision()) {
835       GLVideo.color = 0x3f_ff_00_00;
836       GLVideo.fillRect(x0*scale-xpos, y0*scale-ypos, width*scale, height*scale);
837     }
839     GLVideo.color = oclr;
840   }
841 #endif
845 defaultproperties {
846   active = true;