#include "ht.h" namespace wi { // Implementation Notes: // - Trigger manager: // * loads & manages triggers // * has framework for trigger eval // * maintains per-side evaluation order. // - Trigger // * holder for conditions and actions // * understands 'blocking' actions // // Triggers get evaluated in per-side order // Individual triggers can have actions that are in mid-execution // // Specific conditions and actions are each typed out. // // TriggerMgr // TriggerMgr::TriggerMgr() { m_ctgr = 0; m_atgr = NULL; memset(m_mpSide2nTrigger, 0xff, sizeof(m_mpSide2nTrigger)); memset(m_asidmCondition, 0, sizeof(m_asidmCondition)); memset(m_abSwitch, 0, sizeof(m_abSwitch)); m_cTimers = 0; m_fEnabled = true; } TriggerMgr::~TriggerMgr() { delete[] m_atgr; m_ctgr = 0; } bool TriggerMgr::Init(IniReader *pini) { // Get trigger count int cArgs = pini->GetPropertyValue("Triggers", "Count", "%d", &m_ctgr); if (cArgs != 1) return false; // Allocate trigger storage, one chunk Assert(m_atgr == NULL); m_atgr = new Trigger[m_ctgr]; Assert(m_atgr != NULL, "out of memory!"); if (m_atgr == NULL) return false; // Skip the Count property char szProp[128]; FindProp find; if (!pini->FindNextProperty(&find, "Triggers", szProp, sizeof(szProp))) return false; #ifdef _DEBUG if (strcmp(szProp, "Count") != 0) return false; #endif // Initialize the triggers for (int ntgr = 0; ntgr < m_ctgr; ntgr++) { // Next property name should be "T" if (!pini->FindNextProperty(&find, "Triggers", szProp, sizeof(szProp))) return false; if (strcmp(szProp, "T") != 0) return false; // Remember per-side assignments and indexes int cArgs = pini->GetPropertyValue(&find, "%s", szProp); if (cArgs != 1) return false; if (!AssignTriggerSides(ntgr, szProp)) return false; // Initialize this trigger if (!m_atgr[ntgr].Init(pini, &find)) return false; } m_tLastUpdate = gsim.GetTickCount(); #ifdef DEBUG_HELPERS FindProp findSwitch; int isw = 0; while (pini->FindNextProperty(&findSwitch, "Switches", szProp, sizeof(szProp))) { strncpyz(m_aszSwitchNames[isw], szProp, sizeof(m_aszSwitchNames[0])); isw++; Assert(isw <= kcSwitchMax); } extern void UpdateTriggerViewer(); UpdateTriggerViewer(); #endif return true; } #define knVerTriggerMgrState 2 bool TriggerMgr::LoadState(Stream *pstm) { byte nVer = pstm->ReadByte(); if (nVer != knVerTriggerMgrState) return false; // Read Triggers Trigger *ptgr = m_atgr; int i; // thank you WinCE compiler for this for (i = 0; i < this->m_ctgr; i++, ptgr++) ptgr->LoadState(pstm); // Read Switch state pstm->ReadBytesRLE(m_abSwitch, sizeof(m_abSwitch)); // Read one-update conditions pstm->ReadBytesRLE((byte *)m_asidmCondition, sizeof(m_asidmCondition)); // Read timer countdown values for (i = 0; i < m_cTimers; i++) m_actCountdown[i] = pstm->ReadDword(); m_tLastUpdate = pstm->ReadDword(); // load the game countdown timer m_cdt.LoadState(pstm); return pstm->IsSuccess(); } bool TriggerMgr::SaveState(Stream *pstm) { pstm->WriteByte(knVerTriggerMgrState); // Write Triggers Trigger *ptgr = m_atgr; int i; // Thank you WinCE compiler for this for (i = 0; i < this->m_ctgr; i++, ptgr++) ptgr->SaveState(pstm); // Write Switch state pstm->WriteBytesRLE(m_abSwitch, sizeof(m_abSwitch)); // Write one-update conditions pstm->WriteBytesRLE((byte *)m_asidmCondition, sizeof(m_asidmCondition)); // Write timer countdown values for (i = 0; i < m_cTimers; i++) pstm->WriteDword(m_actCountdown[i]); pstm->WriteDword((dword)m_tLastUpdate); m_cdt.SaveState(pstm); return pstm->IsSuccess(); } bool TriggerMgr::AssignTriggerSides(int ntgr, char *psz) { Assert(ntgr >= 0 && ntgr < m_ctgr); while (true) { int side; int nIndex; int nch = 0; int cArgs = IniScanf(psz, "%d:%d,%+", &side, &nIndex, &nch); if (cArgs == 0) break; if (cArgs != 2 && cArgs != 3) return false; psz += nch; Assert(side >= ksideNeutral && side < kcSides); Assert(nIndex >= 0 && nIndex < kcTriggersPerSide); if (nIndex < kcTriggersPerSide) m_mpSide2nTrigger[side][nIndex] = (byte)ntgr; if (cArgs == 2) break; } return true; } void TriggerMgr::Update() { if (!m_fEnabled) return; m_cdt.Update(); // Update all the trigger timers long t = gsim.GetTickCount(); long dt = t - m_tLastUpdate; m_tLastUpdate = t; int *pct = m_actCountdown; int i; // thank you WinCE compiler for this for (i = 0; i < m_cTimers; i++) if (*pct != kctTimerNotStarted) *pct++ -= dt; // For each side... for (Side side = ksideNeutral; side < kcSides; side++) { // Execute per-side triggers in the per-side specified order byte *pntgr = &m_mpSide2nTrigger[side][0]; for (; *pntgr != 0xff; pntgr++) { Assert(*pntgr < m_ctgr); // Subsequent triggers may have been disabled by the prior trigger // (e.g., End Mission action) if (m_fEnabled) m_atgr[*pntgr].Execute(side); } } // Clear one-Update conditions memset(m_asidmCondition, 0, sizeof(m_asidmCondition)); // Reset any underflowed timers pct = m_actCountdown; for (i = 0; i < m_cTimers; i++) { if (*pct <= 0 && *pct != kctTimerNotStarted) *pct += m_actPeriod[i]; pct++; } #ifdef DEBUG_HELPERS extern void UpdateTriggerViewer(); UpdateTriggerViewer(); #endif } void TriggerMgr::SetConditionTrue(int nCondition, SideMask sidm) { m_asidmCondition[nCondition] |= sidm; } bool TriggerMgr::IsConditionTrue(int nCondition, SideMask sidm) { return (m_asidmCondition[nCondition] & sidm) != 0; } // // Trigger // Trigger::Trigger() { m_pcdn = NULL; m_pactn = NULL; memset(m_apactnLast, 0, sizeof(m_apactnLast)); memset(m_afArmed, 1, sizeof(m_afArmed)); } Trigger::~Trigger() { // Delete Conditions Condition *pcdn = m_pcdn; while (pcdn != NULL) { Condition *pcdnNext = pcdn->m_pcdnNext; delete pcdn; pcdn = pcdnNext; } // Delete Actions TriggerAction *pactn = m_pactn; while (pactn != NULL) { TriggerAction *pactnNext = pactn->m_pactnNext; delete pactn; pactn = pactnNext; } } #define knVerTriggerState 1 bool Trigger::LoadState(Stream *pstm) { byte nVer = pstm->ReadByte(); if (nVer != knVerTriggerState) return false; // Read the last (in-progress) action (if any) for each side for (int i = 0; i < kcSides; i++) { char nAction = pstm->ReadByte(); if (nAction != -1) { TriggerAction *pactn = m_pactn; for (int j = 0; j < nAction; j++, pactn = pactn->m_pactnNext); m_apactnLast[i] = pactn; } } // Read the per-side armed flags pstm->ReadBytesRLE((byte *)m_afArmed, sizeof(m_afArmed)); // Give each Action a chance to read its state for (TriggerAction *pactn = m_pactn; pactn != NULL; pactn = pactn->m_pactnNext) pactn->LoadState(pstm); return pstm->IsSuccess(); } bool Trigger::SaveState(Stream *pstm) { pstm->WriteByte(knVerTriggerState); // Map m_apactnLast into an index or -1 for no last action for (int i = 0; i < kcSides; i++) { if (m_apactnLast[i] == NULL) { pstm->WriteByte((byte)-1); } else { char nAction = 0; for (TriggerAction *pactn = m_pactn; pactn != NULL; pactn = pactn->m_pactnNext, nAction++) if (pactn == m_apactnLast[i]) break; pstm->WriteByte(nAction); } } // Write the per-side armed flags pstm->WriteBytesRLE((byte *)m_afArmed, sizeof(m_afArmed)); // Give each Action a chance to write its state for (TriggerAction *pactn = m_pactn; pactn != NULL; pactn = pactn->m_pactnNext) pactn->SaveState(pstm); return pstm->IsSuccess(); } bool Trigger::Init(IniReader *pini, FindProp *pfind) { // Property names // 'T' means start of new trigger // 'C' means condition // 'A' means action char szProp[16]; FindProp findLast; findLast.Assign(pfind); while (pini->FindNextProperty(pfind, "Triggers", szProp, sizeof(szProp))) { switch (szProp[0]) { case 'T': // Return the last PropFind since TriggerMgr wants to find a 'T' next pfind->Assign(&findLast); return true; case 'C': if (!LoadCondition(pini, pfind)) return false; break; case 'A': if (!LoadAction(pini, pfind)) return false; break; default: Assert(false); break; } findLast.Assign(pfind); } return true; } // Execute will either resume at a blocking action or check conditions for true-ness and then // perform actions. An action just needs to return false to "block" and be resumed later. void Trigger::Execute(Side side, bool fForce) { // Is this trigger still executing an action for this side? TriggerAction *pactnNext = m_apactnLast[side]; if (pactnNext == NULL) { // If not armed, then this trigger no longer executes for this side if (!fForce) { if (!m_afArmed[side]) return; // It's armed so we check conditions. Return if any condition is not true. for (Condition *pcdn = m_pcdn; pcdn != NULL; pcdn = pcdn->m_pcdnNext) { if (!pcdn->IsTrue(side)) return; } } // The conditions are met so we're going to perform the actions. Disarm the // trigger so its actions only happen once, unless a PreserveTriggerAction // is used to rearm it. m_afArmed[side] = false; } // Start executing actions. If pactnNext != NULL, start with that action if (pactnNext == NULL) pactnNext = m_pactn; while (pactnNext != NULL) { // If this action doesn't complete, remember that and return m_apactnLast[side] = pactnNext; #ifdef DEBUG_HELPERS extern void UpdateTriggerViewer(); UpdateTriggerViewer(); #endif if (!pactnNext->Perform(this, side)) return; // Action complete, continue to next action m_apactnLast[side] = NULL; // clear out 'in-progress' action pactnNext = pactnNext->m_pactnNext; } } void Trigger::SetCurrentActionComplete(Side side) { m_apactnLast[side] = NULL; // clear out 'in-progress' action } void Trigger::Arm(Side side) { m_afArmed[side] = true; } // // Condition/Action parameter types // bool QualifiedNumber::Parse(char **ppsz) { int nch = 0; int cArgs = IniScanf(*ppsz, "%d,%ld,%+", &m_nQualifier, &m_nNumber, &nch); *ppsz += nch; return cArgs >= 2; } bool QualifiedNumber::Compare(long nNumber) { switch (m_nQualifier) { case knQualifierAtLeast: return nNumber >= m_nNumber; case knQualifierAtMost: return nNumber <= m_nNumber; case knQualifierExactly: return nNumber == m_nNumber; } return false; } // countdown timer class CountdownTimer::CountdownTimer() { m_szFormat[0] = 0; m_secs = 0; m_tLast = 0; m_wf = 0; } #define knVerCountdownTimerState 1 bool CountdownTimer::LoadState(Stream *pstm) { byte nVer = pstm->ReadByte(); if (nVer != knVerCountdownTimerState) return false; m_secs = (short)pstm->ReadWord(); m_wf = pstm->ReadWord(); m_tLast = pstm->ReadDword(); pstm->ReadString(m_szFormat, sizeof(m_szFormat)); return pstm->IsSuccess(); } bool CountdownTimer::SaveState(Stream *pstm) { pstm->WriteByte(knVerCountdownTimerState); pstm->WriteWord(m_secs); word wf = ggame.GetSimUIForm()->GetControlPtr(kidcCountdown)->GetFlags(); m_wf = (wf & kfCtlVisible) ? (m_wf | kfCtVisibleAtStart) : (m_wf & ~kfCtVisibleAtStart); pstm->WriteWord(m_wf); pstm->WriteDword((dword)m_tLast); pstm->WriteString(m_szFormat); return pstm->IsSuccess(); } void CountdownTimer::SetTimer(int csecs, char *pszFormatString) { m_secs = csecs; strncpyz(m_szFormat, pszFormatString, sizeof(m_szFormat)); } void CountdownTimer::StartTimer(bool fStart) { m_wf = (m_wf & ~kfCtRunning); if (fStart) { m_wf |= kfCtRunning; m_tLast = gsim.GetTickCount(); UpdateString(); } } void CountdownTimer::ShowTimer(bool fShow) { ggame.GetSimUIForm()->GetControlPtr(kidcCountdown)->Show(fShow); } bool CountdownTimer::GetTimer(int *psecs) { *psecs = m_secs; return (m_wf & kfCtRunning) != 0; } void CountdownTimer::Update() { if (m_wf & kfCtRunning && (m_secs > 0)) { long t = gsim.GetTickCount(); long dt = t - m_tLast; if (dt > 100) // greater than 1 second { m_secs -= 1; m_tLast += 100; UpdateString(); } } } void CountdownTimer::UpdateString() { char szT[50]; char szTemp[15]; // bummer! Palm does not support this format string //sprintf(szTemp, "%.2d:%.2d", m_secsCountdown / 60, m_secsCountdown % 60); int cMins = m_secs / 60; int cSecs = m_secs % 60; int cBytes = sprintf(szTemp, "%s%d:%s%d", cMins > 9 ? "" : "0", cMins, cSecs > 9 ? "" : "0", cSecs); sprintf(szT, m_szFormat, szTemp); LabelControl *plbl = (LabelControl *)ggame.GetSimUIForm()->GetControlPtr(kidcCountdown); plbl->SetText(szT); } // // Parsers, helpers & interesting datatypes // UNDONE: a scanf-like general parser to make condition/action parsers super easy to write // E.g. for AreaContainsUnitsCondition: // CaParse("dqud", &m_nArea, &m_qnum, &m_um, &m_nCaSideMask) // SideMask GetSideMaskFromCaSideMask(Side sideCur, word wfCaSideMask) { Assert(knCaSideSide1 == kside1 && knCaSideSide2 == kside2 && knCaSideSide3 == kside3 && knCaSideSide4 == kside4); // TODO: this approach is expensive at run-time SideMask sidm = wfCaSideMask & ksidmAll; if (wfCaSideMask & (1 << knCaSideAllSides)) { sidm |= ksidmAll; } else { if (wfCaSideMask & (1 << knCaSideAllies)) { Player *pplr = gplrm.GetPlayer(sideCur); sidm |= pplr->GetAllies(); } if (wfCaSideMask & (1 << knCaSideEnemies)) { Player *pplr = gplrm.GetPlayer(sideCur); sidm |= ksidmAll & ~pplr->GetAllies(); } if (wfCaSideMask & (1 << knCaSideCurrentSide)) sidm |= GetSideMask(sideCur); } return sidm; } int GetPlayersListFromCaSideMask(Side sideCur, word wfMask, Player **applr) { SideMask sidm = GetSideMaskFromCaSideMask(sideCur, wfMask); Player *pplr = NULL; int i = 0; while (true) { pplr = gplrm.GetNextPlayer(pplr); if (pplr == NULL) break; if (sidm & GetSideMask(pplr->GetSide())) applr[i++] = pplr; } return i; } bool ParseNumber(char **ppsz, int *pn) { int nch = 0; int cArgs = IniScanf(*ppsz, "%d,%+", pn, &nch); // If this is the last of a comma separated list of items set the // string pointer to point to the zero-terminator so subsequent // calls will fail. if (cArgs == 1) { *ppsz += strlen(*ppsz); } else { *ppsz += nch; } return cArgs >= 1; } bool ParseLong(char **ppsz, long *pn) { int nch = 0; int cArgs = IniScanf(*ppsz, "%ld,%+", pn, &nch); // If this is the last of a comma separated list of items set the // string pointer to point to the zero-terminator so subsequent // calls will fail. if (cArgs == 1) { *ppsz += strlen(*ppsz); } else { *ppsz += nch; } return cArgs >= 1; } bool ParseArea(char **ppsz, int *pn) { if (!ParseNumber(ppsz, pn)) return false; Assert(*pn >= knAreaLastDiscovery, "Detected reference to invalid (deleted?) Area"); return true; } bool ParseUnitMask(char **ppsz, UnitMask *pum) { *pum = 0; int nch = 0; int cArgs = IniScanf(*ppsz, "%d,%+", pum, &nch); *ppsz += nch; return cArgs >= 1; } bool ParseUpgradeMask(char **ppsz, UpgradeMask *pupgm) { int upgm = 0; int nch = 0; int cArgs = IniScanf(*ppsz, "%d,%+", &upgm, &nch); *ppsz += nch; *pupgm = (word)upgm; return cArgs >= 1; } bool ParseString(char **ppsz, char *psz) { int nch = 0; int cArgs = IniScanf(*ppsz, "%s,%+", psz, &nch); // If this is the last of a comma separated list of items set the // string pointer to point to the zero-terminator so subsequent // calls will fail. if (cArgs == 1) { *ppsz += strlen(*ppsz); } else { *ppsz += nch; } return cArgs >= 1; } } // namespace wi