221
%{
#include "StdH.h"
#include <Engine/CurrentVersion.h>
%}

uses "Entities/KeyItem";
uses "Entities/Player";

enum DoorType {
  0 DT_AUTO       "Auto",       // opens automatically
  1 DT_TRIGGERED  "Triggered",  // opens when triggered
  2 DT_LOCKED     "Locked",     // requires a key
  3 DT_TRIGGEREDAUTO     "Triggered Auto",     // opens automatically after being triggered
};

class CDoorController : CRationalEntity {
name      "DoorController";
thumbnail "Thumbnails\\DoorController.tbn";
features  "HasName", "IsTargetable";

properties:
  1 CTString m_strName          "Name" 'N' = "DoorController",
  2 CTString m_strDescription = "",
  3 CEntityPointer m_penTarget1  "Target1" 'T' COLOR(C_MAGENTA|0xFF),
  4 CEntityPointer m_penTarget2  "Target2" COLOR(C_MAGENTA|0xFF),
  5 FLOAT m_fWidth              "Width"  'W' = 2.0f,
  6 FLOAT m_fHeight             "Height" 'H' = 3.0f,
  7 BOOL m_bPlayersOnly         "Players Only" 'P' = TRUE,
  8 enum DoorType m_dtType      "Type" 'Y' = DT_AUTO,
  9 CTStringTrans m_strLockedMessage "Locked message" 'L' = "",
  13 CEntityPointer m_penLockedTarget  "Locked target" COLOR(C_dMAGENTA|0xFF),   // target to trigger when locked
  12 enum KeyItemType m_kitKey  "Key" 'K' = KIT_ANKHWOOD,  // key type (for locked door)
  14 BOOL m_bTriggerOnAnything "Trigger on anything" = FALSE,
  15 BOOL m_bActive "Active" 'A' = TRUE,    // automatic door function can be activated/deactivated

  10 BOOL m_bLocked = FALSE,  // for lock/unlock door
  11 CEntityPointer m_penCaused,    // for trigger relaying

components:
  1 model   MODEL_DOORCONTROLLER     "Models\\Editor\\DoorController.mdl",
  2 texture TEXTURE_DOORCONTROLLER   "Models\\Editor\\DoorController.tex",

functions:
  CEntity *GetTarget(void) const { return m_penTarget1; };

  const CTString &GetDescription(void) const {
    if (m_penTarget1!=NULL && m_penTarget2!=NULL) {
      ((CTString&)m_strDescription).PrintF("->%s,%s", 
        m_penTarget1->GetName(), m_penTarget2->GetName());
    } else if (m_penTarget1!=NULL) {
      ((CTString&)m_strDescription).PrintF("->%s", 
        m_penTarget1->GetName());
    } else {
      ((CTString&)m_strDescription).PrintF("-><none>");
    }
    return m_strDescription;
  }

  // test if this door reacts on this entity
  BOOL CanReactOnEntity(CEntity *pen)
  {
    if (pen==NULL) {
      return FALSE;
    }
    // never react on non-live or dead entities
    if (!(pen->GetFlags()&ENF_ALIVE)) {
      return FALSE;
    }

    if (m_bPlayersOnly && !IsDerivedFromClass(pen, "Player")) {
      return FALSE;
    }

    return TRUE;
  }

  // test if this door can be triggered by this entity
  BOOL CanTriggerOnEntity(CEntity *pen)
  {
    return m_bTriggerOnAnything || CanReactOnEntity(pen);
  }

  void TriggerDoor(void)
  {
    if (m_penTarget1!=NULL) {
      SendToTarget(m_penTarget1, EET_TRIGGER, m_penCaused);
    }
    if (m_penTarget2!=NULL) {
      SendToTarget(m_penTarget2, EET_TRIGGER, m_penCaused);
    }
  }

  // apply mirror and stretch to the entity
  void MirrorAndStretch(FLOAT fStretch, BOOL bMirrorX)
  {
    // stretch its ranges
    m_fWidth*=fStretch;
    m_fHeight*=fStretch;
  }

procedures:

  // entry point for automatic functioning
  DoorAuto()
  {
    // go into active or inactive state
    if (m_bActive) {
      jump DoorAutoActive();
    } else {
      jump DoorAutoInactive();
    }
  }

  // automatic door active state
  DoorAutoActive()
  {
    ASSERT(m_bActive);
    while (TRUE) {
      // wait 
      wait() {
        // when someone enters
        on (EPass ePass) : {
          // if he can open the door
          if (CanReactOnEntity(ePass.penOther)) {
            // do it
            m_penCaused = ePass.penOther;
            TriggerDoor();
              
            // this is a very ugly fix for cooperative not finishing in the demo level
            // remove this when not needed any more!!!!
            if(_SE_DEMO && GetSP()->sp_bCooperative && !GetSP()->sp_bSinglePlayer) {
              if (m_strName=="Appear gold amon") {
                CPlayer *penPlayer = (CPlayer*)&*ePass.penOther;
                penPlayer->SetGameEnd();
              }
            }

            resume;
          }
          resume;
        }
        // if door is deactivated
        on (EDeactivate) : {
          // go to inactive state
          m_bActive = FALSE;
          jump DoorAutoInactive();
        }
        otherwise() : {
          resume;
        };
      };
      
      // wait a bit to recover
      autowait(0.1f);
    }
  }

  // automatic door inactive state
  DoorAutoInactive()
  {
    ASSERT(!m_bActive);
    while (TRUE) {
      // wait 
      wait() {
        // if door is activated
        on (EActivate) : {
          // go to active state
          m_bActive = TRUE;
          jump DoorAutoActive();
        }
        otherwise() : {
          resume;
        };
      };
      
      // wait a bit to recover
      autowait(0.1f);
    }
  }

  // door when do not function anymore
  DoorDummy()
  {
    wait() {
      on (EBegin) : {
        resume;
      }
      otherwise() : {
        resume;
      };
    }
  }

  // door that wait to be triggered to open
  DoorTriggered()
  {
    while (TRUE) {
      // wait to someone enter
      wait() {
        on (EPass ePass) : {
          if (CanReactOnEntity(ePass.penOther)) {
            if (m_strLockedMessage!="") {
              PrintCenterMessage(this, ePass.penOther, TranslateConst(m_strLockedMessage), 3.0f, MSS_INFO);
            }
            if (m_penLockedTarget!=NULL) {
              SendToTarget(m_penLockedTarget, EET_TRIGGER, ePass.penOther);
            }
            resume;
          }
        }
        on (ETrigger eTrigger) : {
          m_penCaused = eTrigger.penCaused;
          TriggerDoor();
          jump DoorDummy();
        }
        otherwise() : {
          resume;
        };
      };
      
      // wait a bit to recover
      autowait(0.1f);
    }
  }

  // door that need a key to be unlocked to open
  DoorLocked()
  {
    while (TRUE) {
      // wait to someone enter
      wait() {
        on (EPass ePass) : {
          if (IsDerivedFromClass(ePass.penOther, "Player")) {
            CPlayer *penPlayer = (CPlayer*)&*ePass.penOther;
            // if he has the key
            ULONG ulKey = (1<<INDEX(m_kitKey));
            if (penPlayer->m_ulKeys&ulKey) {
              // use the key
              penPlayer->m_ulKeys&=~ulKey;
              // open the dook
              TriggerDoor();

              /*
              // tell the key bearer that the key was used
              CTString strMsg;
              strMsg.PrintF(TRANS("%s used"), GetKeyName(m_kitKey));
              PrintCenterMessage(this, ePass.penOther, strMsg, 3.0f, MSS_INFO);
              */
              // become automatic door
              jump DoorAuto();
            // if he has no key
            } else {
              if (m_penLockedTarget!=NULL) {
                SendToTarget(m_penLockedTarget, EET_TRIGGER, ePass.penOther);
              }
            }
            resume;
          }
        }
        otherwise() : {
          resume;
        };
      };
      
      // wait a bit to recover
      autowait(0.1f);
    }
  }

  // door that need to be triggered to start working automatically
  DoorTriggeredAuto()
  {
    while (TRUE) {
      // wait to be triggered
      wait() {
        on (ETrigger eTrigger) : {
          // become auto door
          jump DoorAuto();
        }
        on (EPass ePass) : {
          if (CanReactOnEntity(ePass.penOther)) {
            if (m_strLockedMessage!="") {
              PrintCenterMessage(this, ePass.penOther, TranslateConst(m_strLockedMessage), 3.0f, MSS_INFO);
            }
            if (m_penLockedTarget!=NULL) {
              SendToTarget(m_penLockedTarget, EET_TRIGGER, ePass.penOther);
            }
          }
          resume;
        }
        otherwise() : {
          resume;
        };
      };
      
      // wait a bit to recover
      autowait(0.1f);
    }
  }

  Main()
  {
    InitAsEditorModel();
    SetPhysicsFlags(EPF_MODEL_IMMATERIAL);
    SetCollisionFlags(ECF_TOUCHMODEL);

    // set appearance
    GetModelObject()->StretchModel(FLOAT3D(m_fWidth, m_fHeight, m_fWidth));
    SetModel(MODEL_DOORCONTROLLER);
    SetModelMainTexture(TEXTURE_DOORCONTROLLER);
    ModelChangeNotify();

    // don't start in wed
    autowait(0.1f);

    // dispatch to aproppriate loop
    switch(m_dtType) {
    case DT_AUTO: {
      jump DoorAuto();
                  } break;
    case DT_TRIGGERED: {
      jump DoorTriggered();
                       } break;
    case DT_TRIGGEREDAUTO: {
      jump DoorTriggeredAuto();
                       } break;
    case DT_LOCKED: {
      jump DoorLocked();
                    } break;
    }
  }
};

