From f77667d5a01eac4de0dd0cb99c000f81dea3a261 Mon Sep 17 00:00:00 2001 From: James Farris Date: Sun, 28 Mar 2021 21:45:15 -0700 Subject: [PATCH] UndeadTargetingComponent tries to reissue attack orders against specific units Fixed a bug where spawn waves would trigger for the first player once before everyone else --- _build/dependencies/WurstCore | 2 +- .../UndeadTargetingComponent.wurst | 142 +++++++----------- .../Generation/NormalNightWaveGenerator.wurst | 42 +++--- wurst/Game/Waves/SpawnWave.wurst | 51 ++++--- wurst/Undead/Abomination/Abomination.wurst | 15 +- wurst/Undead/CryptFiendComponent.wurst | 11 -- .../Necromancer/NecromancerComponent.wurst | 14 +- wurst/Utility/RealTime.wurst | 8 +- 8 files changed, 124 insertions(+), 161 deletions(-) diff --git a/_build/dependencies/WurstCore b/_build/dependencies/WurstCore index cf2a9e62..6da558a4 160000 --- a/_build/dependencies/WurstCore +++ b/_build/dependencies/WurstCore @@ -1 +1 @@ -Subproject commit cf2a9e625d35a876148f1751440f9d644e854d99 +Subproject commit 6da558a4e12f6b2b932ecde43d12bc5766aa64e9 diff --git a/wurst/Composition/UndeadTargetingComponent.wurst b/wurst/Composition/UndeadTargetingComponent.wurst index 03279749..807e47cc 100644 --- a/wurst/Composition/UndeadTargetingComponent.wurst +++ b/wurst/Composition/UndeadTargetingComponent.wurst @@ -13,19 +13,23 @@ import Unit_WoodFence import Unit_StoneWall import Unit_GateCommon import Unit_MetalWall -import GameConstants import Orders import TargetUtility import UnitExtensions import ClosureTimers -import TimerUtils +import RealTime +import Dispatcher +constant real ATTACK_TIMER_INTERVAL = 1.0 constant real ATTACK_TIMER_DURATION = 10.0 + HashMap> g_validHumanStructureTargets HashList g_validTargetStructureIds conditionfunc g_filterUnitIsValidTargetStructure = Condition(function filterUnitIsValidTargetStructure) conditionfunc g_filterUnitIsValidTargetNonStructure = Condition(function filterUnitIsValidTargetNonStructure) HashMap> g_targetedUnitToUTCMap = new HashMap>() +HashList g_activeUndeadTargetingComponents = new HashList() +Dispatcher g_issueOrderDispatcher /* @@ -59,10 +63,8 @@ public class UndeadTargetingComponent extends UnitComponent private player m_targetPlayer private Event m_targetPlayerChangedEvent private unit m_targetUnit - private CallbackManual m_attackTimer - - // Not owned! - private HashList m_ignoreOrderIdsList + private int m_attackTime + private bool m_awaitingOrder // -------------------------------------------------------------------------- construct(IUnitMetadata owner) @@ -70,10 +72,6 @@ public class UndeadTargetingComponent extends UnitComponent m_targetPlayer = null m_targetUnit = null - m_attackTimer = getTimer().doManual(ATTACK_TIMER_DURATION, true) () -> - m_targetUnit = null - issueOrderTargetingPlayer() - // -------------------------------------------------------------------------- ondestroy if (m_targetPlayerChangedEvent != null) @@ -84,6 +82,16 @@ public class UndeadTargetingComponent extends UnitComponent override function getTypeId() returns int return UndeadTargetingComponent.typeId + // -------------------------------------------------------------------------- + override function onEnabled() + super.onEnabled() + g_activeUndeadTargetingComponents.add(this) + + // -------------------------------------------------------------------------- + override function onDisabled() + super.onDisabled() + g_activeUndeadTargetingComponents.remove(this) + // -------------------------------------------------------------------------- function getTargetPlayerChangedEvent() returns Event if (m_targetPlayerChangedEvent == null) @@ -109,15 +117,29 @@ public class UndeadTargetingComponent extends UnitComponent return true // -------------------------------------------------------------------------- - function getIgnoreOrderIdsList() returns HashList - return m_ignoreOrderIdsList + function issueOrderTargetingPlayer() - // -------------------------------------------------------------------------- - function setIgnoreOrderIdsList(HashList orderIds) - m_ignoreOrderIdsList = orderIds + if (not getOwnerUnit().isAlive()) + return + + // We're already awaiting a call to issueOrderTargetingPlayer_impl so early out + if (m_awaitingOrder) + return + + m_awaitingOrder = true + + // Dispatch this operation to limit how often this gets executed because + // finding the nearest unit can be expensive + g_issueOrderDispatcher.invoke(() -> issueOrderTargetingPlayer_impl(), DispatchPriority.LOW) // -------------------------------------------------------------------------- - function issueOrderTargetingPlayer() + private function issueOrderTargetingPlayer_impl() + + m_awaitingOrder = false + restartAttackTimer() + + if (not getOwnerUnit().isAlive()) + return if (tryReissueOrder()) return @@ -129,7 +151,7 @@ public class UndeadTargetingComponent extends UnitComponent return // At this point the target unit must not have been valid - m_targetUnit = null + setTargetUnit(null) // 3. Try attack-moving to the target player's camp center (the OT or the hero) if (tryTargetCampCenter()) @@ -219,10 +241,6 @@ public class UndeadTargetingComponent extends UnitComponent undeadUnit.issuePointOrder("attack", campCenter) return true - // -------------------------------------------------------------------------- - protected function increaseAcquisitionRange() - getOwnerUnit().setField(UNIT_RF_ACQUISITION_RANGE, 1024) - // -------------------------------------------------------------------------- protected function onTargetUnitDied() setTargetUnit(null) @@ -238,9 +256,18 @@ public class UndeadTargetingComponent extends UnitComponent m_targetPlayerChangedEvent.call() // -------------------------------------------------------------------------- - private function restartAttackTimer() - if (m_attackTimer != null) - m_attackTimer.restart() + protected function onAttackTimer() + if (m_awaitingOrder) + return + // If the timer has expired then this unit hasn't been able to attack + // it's target in a while so try to target a new enemy + if (getRealTimeSeconds() - m_attackTime > ATTACK_TIMER_DURATION) + setTargetUnit(null) + issueOrderTargetingPlayer() + + // -------------------------------------------------------------------------- + protected function restartAttackTimer() + m_attackTime = getRealTimeSeconds() // ============================================================================ public function IUnitMetadata.getUndeadTargetingComponent() returns UndeadTargetingComponent @@ -347,60 +374,6 @@ function populateInitialValidHumanStructureTargets() cond.destr() tempGroup.release() -// ============================================================================ -function onUnitIssuedTargetOrder() - let orderedUnit = GetOrderedUnit() - let orderId = GetIssuedOrderId() - - if (orderedUnit.getOwner() != PLAYER_UNDEAD or orderId == OrderIds.attack) - return - - let comp = orderedUnit.getMetadata().getUndeadTargetingComponent() - if (comp == null) - return - - let ignoreList = comp.getIgnoreOrderIdsList() - if (ignoreList != null and ignoreList.has(orderId)) - return - - comp.issueOrderTargetingPlayer() - -// ============================================================================ -function onUnitIssuedOrder() - let orderedUnit = GetOrderedUnit() - let orderId = GetIssuedOrderId() - - if (orderedUnit.getOwner() != PLAYER_UNDEAD or orderId == OrderIds.attack) - return - - let comp = orderedUnit.getMetadata().getUndeadTargetingComponent() - if (comp == null) - return - - let ignoreList = comp.getIgnoreOrderIdsList() - if (ignoreList != null and ignoreList.has(orderId)) - return - - comp.issueOrderTargetingPlayer() - -// ============================================================================ -function onUnitIssuedPointOrder() - let orderedUnit = GetOrderedUnit() - let orderId = GetIssuedOrderId() - - if (orderedUnit.getOwner() != PLAYER_UNDEAD or orderId == OrderIds.attack) - return - - let comp = orderedUnit.getMetadata().getUndeadTargetingComponent() - if (comp == null) - return - - let ignoreList = comp.getIgnoreOrderIdsList() - if (ignoreList != null and ignoreList.has(orderId)) - return - - comp.issueOrderTargetingPlayer() - // ============================================================================ function registerTargetedUnit(unit targetedUnit, UndeadTargetingComponent comp) HashList attackerList @@ -432,6 +405,8 @@ function unregisterTargetedUnit(unit targetedUnit) init g_validHumanStructureTargets = new HashMap>() + g_issueOrderDispatcher = new Dispatcher(1024, 1024, 1024)..setMaxExecutions(50) + for p in g_PlayingHumanPlayers g_validHumanStructureTargets.put(p, new LinkedList()) @@ -453,10 +428,9 @@ init registerPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function onUnitDeath) registerPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function onUnitAttacked) - // If an undead unit is issued ANY order other than attack the given target unit then reissue the order to attack the target unit - registerPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function onUnitIssuedPointOrder) - registerPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function onUnitIssuedTargetOrder) - registerPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function onUnitIssuedOrder) - // Populate with initial structures (for debug purposes) - populateInitialValidHumanStructureTargets() \ No newline at end of file + populateInitialValidHumanStructureTargets() + + doPeriodically(ATTACK_TIMER_INTERVAL) (CallbackPeriodic cb) -> + for comp in g_activeUndeadTargetingComponents + g_issueOrderDispatcher.invoke(() -> comp.onAttackTimer(), DispatchPriority.HIGH) \ No newline at end of file diff --git a/wurst/Game/Waves/Generation/NormalNightWaveGenerator.wurst b/wurst/Game/Waves/Generation/NormalNightWaveGenerator.wurst index 2c4d6f77..3c3b5c49 100644 --- a/wurst/Game/Waves/Generation/NormalNightWaveGenerator.wurst +++ b/wurst/Game/Waves/Generation/NormalNightWaveGenerator.wurst @@ -39,6 +39,7 @@ constant time NIGHT_WAVE_END_TIME = NIGHT_WAVE_START_TIME + WAVE_NIGHT_LENGTH constant int MAX_SPAWN_WAVES = 2 SpawnWaveDefinition array[TOTAL_NIGHTS * MAX_SPAWN_WAVES] g_spawnWaveDefinitions +ItemSet array[TOTAL_NIGHTS] g_coinItemSets // ============================================================================ function setSpawnWaveDef(int night, int spawnWaveIndex, SpawnWaveDefinition def) @@ -133,6 +134,22 @@ function defineSpawnWaves() // 14 Wraiths (Invade, Ethereal, Melee) setSpawnWaveDef(gameProgress.nextWave(), 1, createSpawnWave_Wraiths(gameProgress)) + // Generate coin item sets for each wave + gameProgress.setCurrentWaveNumber(1) + for i = 0 to TOTAL_NIGHTS - 1 + gameProgress.nextWave() + let gameT = gameProgress.getCurrentGameProgress() + let coinItemSet = new ItemSet("Coins", COLOR_GOLD.withoutAlpha()) + let weightSmall = g_coinSmallWeightTFunc.call(gameT) + let weightMedium = g_coinMediumWeightTFunc.call(gameT) + let weightLarge = g_coinLargeWeightTFunc.call(gameT) + let weightEpic = g_coinEpicWeightTFunc.call(gameT) + coinItemSet.add(TlsItemIds.coinSmall, weightSmall) + coinItemSet.add(TlsItemIds.coinMedium, weightMedium) + coinItemSet.add(TlsItemIds.coinLarge, weightLarge) + coinItemSet.add(TlsItemIds.coinEpic, weightEpic) + g_coinItemSets[i] = coinItemSet + // ============================================================================ public class NormalNightWaveGenerator implements IWaveGenerator @@ -721,20 +738,8 @@ function getProgressForSpawnWave(IGameWaveProgress gameProgress) returns IProgre ..setDuration(WAVE_NIGHT_LENGTH) // ============================================================================ -function getCoinItemSetForWave(real gameT) returns ItemSet - let itemSet = new ItemSet("Coins", COLOR_GOLD.withoutAlpha()) - - let weightSmall = g_coinSmallWeightTFunc.call(gameT) - let weightMedium = g_coinMediumWeightTFunc.call(gameT) - let weightLarge = g_coinLargeWeightTFunc.call(gameT) - let weightEpic = g_coinEpicWeightTFunc.call(gameT) - - itemSet.add(TlsItemIds.coinSmall, weightSmall) - itemSet.add(TlsItemIds.coinMedium, weightMedium) - itemSet.add(TlsItemIds.coinLarge, weightLarge) - itemSet.add(TlsItemIds.coinEpic, weightEpic) - - return itemSet +function getCoinItemSetForWave(int waveIndex) returns ItemSet + return g_coinItemSets[waveIndex] // ============================================================================ function unit.standardEliteMaxHp(real gameT) @@ -813,13 +818,16 @@ function unit.standardGrantCoinsOnDeath(real gameT) // ============================================================================ function unit.standardGrantCoinsOnDeath(real gameT, real chanceScale) + let waveNumber = (TOTAL_NIGHTS * gameT).floor() + let coinItemSet = getCoinItemSetForWave(waveNumber) let chance = lerp(COIN_CHANCE_RANGE.min, COIN_CHANCE_RANGE.max, gameT) * chanceScale - let itemSet = getCoinItemSetForWave(gameT) - this.awardRandomItemOnDeath(itemSet, chance) + this.awardRandomItemOnDeath(coinItemSet, chance) // ============================================================================ function unit.guaranteeGrantCoinsOnDeath(real gameT) - this.awardRandomItemOnDeath(getCoinItemSetForWave(gameT), 1.0) + let waveNumber = (TOTAL_NIGHTS * gameT).floor() + let coinItemSet = getCoinItemSetForWave(waveNumber) + this.awardRandomItemOnDeath(coinItemSet, 1.0) // ============================================================================ init diff --git a/wurst/Game/Waves/SpawnWave.wurst b/wurst/Game/Waves/SpawnWave.wurst index fe2ae6ea..1a423e15 100644 --- a/wurst/Game/Waves/SpawnWave.wurst +++ b/wurst/Game/Waves/SpawnWave.wurst @@ -16,8 +16,11 @@ import PlayerSpawnPointProviders import UnitMetadata import UndeadTargetingComponent import Runnable +import Dispatcher +import ClosureTimers HashMap g_unitToSpawnWaveMap +Dispatcher g_spawnDispatcher // ============================================================================ public class SpawnWave extends Runnable @@ -65,11 +68,16 @@ public class SpawnWave extends Runnable m_aliveSpawnedUnits.clear() m_numSpawned = 0 - m_definition.getActivator().start() + // Do this one frame later so that all player spawn waves that share these + // spawn def services get started + nullTimer() () -> + //{ + m_definition.getActivator().start() - let runnableProgress = (m_definition.getProgress() castTo int) castTo IRunnable - if (runnableProgress != null) - runnableProgress.start() + let runnableProgress = (m_definition.getProgress() castTo int) castTo IRunnable + if (runnableProgress != null) + runnableProgress.start() + //} // -------------------------------------------------------------------------- override function onCompleted() @@ -78,22 +86,31 @@ public class SpawnWave extends Runnable unlistenToActivatedEvent() unlistenToDefProgressChangedEvent() - m_definition.getActivator().complete() + // Do this one frame later so that all player spawn waves that share these + // spawn def services get complete first + nullTimer() () -> + //{ + m_definition.getActivator().complete() - let runnableProgress = (m_definition.getProgress() castTo int) castTo IRunnable - if (runnableProgress != null) - runnableProgress.complete() + let runnableProgress = (m_definition.getProgress() castTo int) castTo IRunnable + if (runnableProgress != null) + runnableProgress.complete() + //} // -------------------------------------------------------------------------- override function getProgress() returns real return clamp01(m_definition.getProgress().getProgress()) // -------------------------------------------------------------------------- - function activateOnce() + function activate() activateForPlayer(m_targetPlayer) // -------------------------------------------------------------------------- function activateForPlayer(player targetPlayer) + g_spawnDispatcher.invoke(() -> activate(targetPlayer), DispatchPriority.LOW) + + // -------------------------------------------------------------------------- + private function activate(player targetPlayer) if (targetPlayer == null) error("[SpawnWave.onActivated] Target player has not been set") @@ -129,8 +146,6 @@ public class SpawnWave extends Runnable var numSpawnsAvailable = getNumSpawnsAvailable(aliveCountMax, spawnCountMax) - var numberSpawned = 0 - // For each spawn point for sp in spawnPointsResult.spawnPoints @@ -159,8 +174,7 @@ public class SpawnWave extends Runnable if (numSpawnsAvailable == 0) break - if (spawnSingleUnit(unitType, sp, targetPlayer)) - numberSpawned++ + spawnSingleUnit(unitType, sp, targetPlayer) numSpawnsAvailable = getNumSpawnsAvailable(aliveCountMax, spawnCountMax) @@ -170,9 +184,6 @@ public class SpawnWave extends Runnable // Clean up the spawnPoints list destroy spawnPointsResult.spawnPoints - if (numberSpawned == 0) - log("activateForPlayer", m_targetPlayer.getName() + " : No unit was spawned") - // -------------------------------------------------------------------------- private function getNumSpawnsAvailable(int aliveCountMax, int spawnCountMax) returns int let aliveCount = getAliveCount() @@ -287,7 +298,7 @@ public class SpawnWave extends Runnable if (e == null) return - m_onActivatedHandler = e.register(() -> activateOnce()) + m_onActivatedHandler = e.register(() -> activate()) // -------------------------------------------------------------------------- private function unlistenToActivatedEvent() @@ -334,4 +345,8 @@ function onUnitDeath() // ============================================================================ init g_unitToSpawnWaveMap = new HashMap() - registerPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function onUnitDeath) \ No newline at end of file + registerPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function onUnitDeath) + + nullTimer() () -> + if (g_spawnDispatcher == null) + g_spawnDispatcher = new Dispatcher()..setMaxExecutions(1) \ No newline at end of file diff --git a/wurst/Undead/Abomination/Abomination.wurst b/wurst/Undead/Abomination/Abomination.wurst index e7327941..08884966 100644 --- a/wurst/Undead/Abomination/Abomination.wurst +++ b/wurst/Undead/Abomination/Abomination.wurst @@ -16,7 +16,6 @@ import Math import Interpolation import Orders import TlsUnitIds -import HashList constant real ABOM_THROW_PROJ_DURATION = 1.5 constant real ABOM_THROW_PROJ_HEIGHT = 300.0 @@ -39,8 +38,6 @@ constant string ABOM_PROJECTILE_EFFECT2 = "Abilities\\Weapons\\MeatwagonMissile\ LinkedList g_zombieProjectiles = new LinkedList() trigger g_updateZombieProjectilesTrigger -HashList g_undeadTargetingComponentOrderIgnoreList = new HashList() - // ============================================================================ public class Abomination extends UnitMetadata private unit _holdingZombie @@ -77,11 +74,6 @@ public class Abomination extends UnitMetadata super.onUnitChanged(oldUnit, newUnit) startStateMachine() - if (newUnit != null) - let undeadTargetingComponent = this.getOrAddUndeadTargetingComponent() - if (undeadTargetingComponent != null) - undeadTargetingComponent.setIgnoreOrderIdsList(g_undeadTargetingComponentOrderIgnoreList) - // -------------------------------------------------------------------------- override function onKilled() super.onKilled() @@ -233,7 +225,7 @@ function onAbomThrowZombie(unit caster) stateMachine2.transitionToState(ABOM_IDLE_STATE_ID) abomUnit2.unpauseEx() abomUnit2.setAnimation("stand") - abomUnit2.issuePointOrderById(OrderIds.attack, abomUnit2.getPos()) + abomUnit2.getMetadata().getUndeadTargetingComponent().issueOrderTargetingPlayer() //} // At the peak of the throw animation order the dummy to attack ground @@ -409,7 +401,4 @@ init onAbomGrabbedZombie(caster, target) EventListener.onCast(TlsAbilityIds.abomThrowZombie) (caster) -> - onAbomThrowZombie(caster) - - g_undeadTargetingComponentOrderIgnoreList.add(ABOM_GRAB_ORDER_ID) - g_undeadTargetingComponentOrderIgnoreList.add(ABOM_THROW_ORDER_ID) \ No newline at end of file + onAbomThrowZombie(caster) \ No newline at end of file diff --git a/wurst/Undead/CryptFiendComponent.wurst b/wurst/Undead/CryptFiendComponent.wurst index 08f4706e..fac81d25 100644 --- a/wurst/Undead/CryptFiendComponent.wurst +++ b/wurst/Undead/CryptFiendComponent.wurst @@ -17,8 +17,6 @@ import RealtimeUpdate import ObjectIds import AbilityIds import Vector -import UndeadTargetingComponent -import HashList constant real BURROW_SETTLE_DURATION = 2.0 constant real BURROW_TRAVEL_DURATION = 3.0 @@ -34,8 +32,6 @@ constant real EFFECT_LIFETIME = 1.0 constant conditionfunc g_burrowTargetFilter = Condition(function isFilterUnitValidBurrowTarget) -HashList g_undeadTargetingComponentIgnoreOrderList = new HashList() - // ============================================================================ public class CryptFiendComponent extends UnitComponent private bool m_isBurrowing @@ -63,10 +59,6 @@ public class CryptFiendComponent extends UnitComponent // -------------------------------------------------------------------------- override function onEnabled() super.onEnabled() - - let undeadTargetingComponent = getOwner().getUndeadTargetingComponent() - if (undeadTargetingComponent != null) - undeadTargetingComponent.setIgnoreOrderIdsList(g_undeadTargetingComponentIgnoreOrderList) m_burrowCastTimer = getTimer().doManual(BURROW_CAST_INTERVAL, true) -> burrowToNearestTarget() @@ -390,7 +382,4 @@ function onCryptFiendBurrowCast(unit sender, unit target) // ============================================================================ init EventListener.onTargetCast(TlsAbilityIds.cryptFiendBurrow, (sender, target) -> onCryptFiendBurrowCast(sender, target)) - - // UndeadTargetingComponent - ignore burrow order - g_undeadTargetingComponentIgnoreOrderList.add(TlsAbilityIds.cryptFiendBurrow) \ No newline at end of file diff --git a/wurst/Undead/Necromancer/NecromancerComponent.wurst b/wurst/Undead/Necromancer/NecromancerComponent.wurst index 0dc4457c..f9e02d13 100644 --- a/wurst/Undead/Necromancer/NecromancerComponent.wurst +++ b/wurst/Undead/Necromancer/NecromancerComponent.wurst @@ -2,12 +2,8 @@ package NecromancerComponent import UnitComponent import TlsAbilityIds import ClosureTimers -import UndeadTargetingComponent -import HashList -import Orders constant string RAISEDEAD_IMMEDIATEORDERSTR = "instant" // don't ask me why -HashList g_undeadTargetingComponentIgnoreOrderList = new HashList() // ============================================================================ public class NecromancerComponent extends UnitComponent @@ -41,10 +37,6 @@ public class NecromancerComponent extends UnitComponent castTimer = doPeriodically(cooldown) (CallbackPeriodic cb) -> attemptCast() - let undeadTargetingComponent = getOwner().getUndeadTargetingComponent() - if (undeadTargetingComponent != null) - undeadTargetingComponent.setIgnoreOrderIdsList(g_undeadTargetingComponentIgnoreOrderList) - // -------------------------------------------------------------------------- override function onDisabled() super.onDisabled() @@ -67,8 +59,4 @@ public function IUnitMetadata.getOrAddNecromancerComponent() returns Necromancer var component = this.getNecromancerComponent() if (component == null) component = this.addComponent(new NecromancerComponent(this)) castTo NecromancerComponent - return component - -// ============================================================================ -init - g_undeadTargetingComponentIgnoreOrderList.add(OrderIds.instant) \ No newline at end of file + return component \ No newline at end of file diff --git a/wurst/Utility/RealTime.wurst b/wurst/Utility/RealTime.wurst index fc8eb8d2..f5f6421c 100644 --- a/wurst/Utility/RealTime.wurst +++ b/wurst/Utility/RealTime.wurst @@ -1,10 +1,10 @@ package RealTime import ClosureTimers -real g_realTimeSeconds +int g_realTimeSeconds // ============================================================================ -public function getRealTimeSeconds() returns real +public function getRealTimeSeconds() returns int return g_realTimeSeconds // ============================================================================ @@ -21,14 +21,14 @@ public function getRealTimeString() returns string let hoursStr = hours < 10 ? "0" + hours.toString() : hours.toString() let minutes = R2I(getRealTimeMinutes() - R2I(R2I(getRealTimeHours()) * 60.0)) let minutesStr = minutes < 10 ? "0" + minutes.toString() : minutes.toString() - let seconds = R2I(getRealTimeSeconds() - R2I(R2I(getRealTimeMinutes()) * 60.0)) + let seconds = R2I(getRealTimeSeconds().toReal() - R2I(R2I(getRealTimeMinutes()) * 60.0)) let secondsStr = seconds < 10 ? "0" + seconds.toString() : seconds.toString() return "{0}:{1}:{2}".format(hoursStr, minutesStr, secondsStr) // ============================================================================ init doPeriodically(1.0) (CallbackPeriodic cb) -> - g_realTimeSeconds += 1.0 + g_realTimeSeconds++ // ============================================================================ @test function realTimeString()