PageRenderTime 52ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/src/World.hx

https://bitbucket.org/trethaller/linkndefend-dev
Haxe | 1280 lines | 1054 code | 179 blank | 47 comment | 294 complexity | a80bf117e1216b458a5f5f91cab75174 MD5 | raw file
  1. import flash.Lib;
  2. import flash.display.Sprite;
  3. import flash.display.Shape;
  4. import flash.events.Event;
  5. import flash.events.KeyboardEvent;
  6. import flash.events.MouseEvent;
  7. import flash.ui.Keyboard;
  8. import flash.text.TextField;
  9. import haxe.Serializer;
  10. import haxe.Unserializer;
  11. import haxe.Timer;
  12. import haxe.Log;
  13. import script.Machine;
  14. import script.Program;
  15. import Common;
  16. import Node;
  17. import Link;
  18. import Bug;
  19. import Level;
  20. import Grid;
  21. import HUD;
  22. import CommandMacro;
  23. @:build(CommandMacro.build()) class Commands {}
  24. enum InputMode
  25. {
  26. Default;
  27. CreateNode;
  28. Disabled;
  29. LinkDirection;
  30. }
  31. class WorldProperties
  32. {
  33. /** How many Scout bugs should be automatically
  34. * spawned constantly */
  35. public var autoSpawnScouts:Int;
  36. /** Duration between two autoSpawns (s) */
  37. public var autoSpawnInterval:Float;
  38. /** Will bugs target Core node only
  39. * or attack any node */
  40. public var targetCore:Bool;
  41. public function new() {
  42. targetCore = false;
  43. autoSpawnInterval = 1;
  44. }
  45. }
  46. class WorldState
  47. {
  48. public var nodes:Array<Node>;
  49. public var links:Array<Link>;
  50. public var bugs:List<Bug>;
  51. public var cells:List<Cell>;
  52. public var players:Array<Player>;
  53. public var time:Float;
  54. public var properties:WorldProperties;
  55. public function new()
  56. {
  57. }
  58. }
  59. class BugSpawnHandler implements script.ICommandHandler
  60. {
  61. public function new(w:World)
  62. {
  63. this.world = w;
  64. }
  65. public function handleCommand(cmd:String, arg:String):Bool
  66. {
  67. if (cmd == Commands.target) {
  68. target = cast(world.getNodeByID(arg), BugNestNode);
  69. if (target == null) {
  70. Log.trace("ERROR: could not find target " + arg);
  71. }
  72. return true;
  73. }
  74. else if (cmd == Commands.spawn) {
  75. if (target != null) {
  76. var type = Type.createEnum(BugType, arg);
  77. if (type != null) {
  78. world.spawnBug(target, type);
  79. }
  80. else {
  81. Log.trace("ERROR: could not find bug type " + arg);
  82. }
  83. }
  84. return true;
  85. }
  86. return false;
  87. }
  88. var target:BugNestNode;
  89. var world:World;
  90. }
  91. class WorldCommandHandler implements script.ICommandHandler
  92. {
  93. public function new(w:World)
  94. {
  95. world = w;
  96. spawner = new BugSpawnHandler(w);
  97. }
  98. public function handleCommand(cmd:String, arg:String):Bool
  99. {
  100. if (spawner.handleCommand(cmd, arg))
  101. return true;
  102. if (cmd == Commands.gameLose) {
  103. world.onGameLose(arg);
  104. }
  105. else if (cmd == Commands.msgInfo) {
  106. Log.trace(arg);
  107. }
  108. else if (cmd == Commands.set) {
  109. var args = arg.split(',');
  110. try {
  111. var member = Reflect.field(world.properties, args[0]);
  112. if (Type.typeof(member) == Type.ValueType.TBool) {
  113. Reflect.setField(world.properties, args[0], args[1]=='0' ? false : true);
  114. }
  115. else if (Type.typeof(member) == Type.ValueType.TInt) {
  116. Reflect.setField(world.properties, args[0], Std.parseInt(args[1]));
  117. }
  118. else if (Type.typeof(member) == Type.ValueType.TFloat) {
  119. Reflect.setField(world.properties, args[0], Std.parseFloat(args[1]));
  120. }
  121. else
  122. Reflect.setField(world.properties, args[0], args[1]);
  123. } catch (e:String){ Log.trace("Could not set property: " + e); }
  124. }
  125. else if (cmd == Commands.exec) {
  126. world.executeScriptlet(arg);
  127. }
  128. return false;
  129. }
  130. var spawner:BugSpawnHandler;
  131. var world:World;
  132. }
  133. class World extends Sprite
  134. {
  135. public static var Instance:World;
  136. public var players:Array<Player>;
  137. public var nodes:Array<Node>;
  138. public var links:Array<Link>;
  139. public var properties:WorldProperties;
  140. var bugs:List<Bug>;
  141. var inputMode:InputMode;
  142. var activePlayerIndex:Int;
  143. var grid:Grid;
  144. var script:script.Machine;
  145. var cmdHandler:WorldCommandHandler;
  146. var level:Level;
  147. public var time:Float;
  148. public var selectedNode:Node;
  149. public var nStepsPerFrame:Int;
  150. public var playing:Bool;
  151. // GUI
  152. public var backgroundLayer:Sprite;
  153. public var rootLayer:Sprite;
  154. var gridLayer:GridUI;
  155. var linksLayer:Sprite;
  156. var linksFXLayer:Sprite;
  157. var nodesLayer:Sprite;
  158. var bugsLayer:Sprite;
  159. var effectsLayer:Sprite;
  160. public var hud:HUD;
  161. // Temp
  162. var tempNode:Node;
  163. var tempNodeUI:NodeUI;
  164. public function new()
  165. {
  166. super();
  167. Instance = this;
  168. inputMode = InputMode.Default;
  169. activePlayerIndex = -1;
  170. nStepsPerFrame = 1;
  171. players = new Array<Player>();
  172. playing = false;
  173. properties = new WorldProperties();
  174. init();
  175. }
  176. public function getWidth() { return 800; }
  177. public function getHeight() { return 600; }
  178. public function getPlayer(i:Int): Player
  179. {
  180. return players[i];
  181. }
  182. public function setActivePlayer(i:Int)
  183. {
  184. activePlayerIndex = i;
  185. Debug.assert(getPlayer(i) != null);
  186. }
  187. function init()
  188. {
  189. // Background
  190. backgroundLayer = new Sprite();
  191. backgroundLayer.graphics.clear();
  192. backgroundLayer.graphics.beginFill(0xffffff, 1);
  193. backgroundLayer.graphics.drawRect(0, 0, 800, 600);
  194. backgroundLayer.graphics.endFill();
  195. backgroundLayer.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
  196. backgroundLayer.addEventListener(MouseEvent.MOUSE_UP, mouseUp);
  197. backgroundLayer.addEventListener(MouseEvent.MOUSE_MOVE, mouseMove);
  198. backgroundLayer.addEventListener(MouseEvent.CLICK, mouseClicked);
  199. addChild(backgroundLayer);
  200. resetWorld();
  201. addEventListener(Event.ENTER_FRAME, update);
  202. flash.Lib.current.stage.addEventListener(KeyboardEvent.KEY_DOWN, keydown);
  203. }
  204. public function step(dt:Float)
  205. {
  206. // Update links
  207. for (link in links)
  208. {
  209. link.step(dt);
  210. }
  211. stepNodes();
  212. stepBugs();
  213. grid.update();
  214. script.setTime(time);
  215. time += dt;
  216. }
  217. public function update(e:Event)
  218. {
  219. // Simulation
  220. if (playing) {
  221. for (i in 0...nStepsPerFrame)
  222. {
  223. step(Settings.Gameplay.StepDuration);
  224. }
  225. }
  226. // Keyboard temp
  227. {
  228. var s = 20;
  229. if (KeyboardManager.Instance.getKeyState(Keyboard.LEFT).active) {
  230. rootLayer.x += s;
  231. }
  232. if (KeyboardManager.Instance.getKeyState(Keyboard.RIGHT).active) {
  233. rootLayer.x -= s;
  234. }
  235. if (KeyboardManager.Instance.getKeyState(Keyboard.UP).active) {
  236. rootLayer.y += s;
  237. }
  238. if (KeyboardManager.Instance.getKeyState(Keyboard.DOWN).active) {
  239. rootLayer.y -= s;
  240. }
  241. }
  242. gridLayer.moveTo(rootLayer.x, rootLayer.y);
  243. }
  244. public function spawnBug(nest:BugNestNode, type:BugType)
  245. {
  246. var b = new Bug();
  247. b.pos.x = Math.random() * 5 + nest.pos.x;
  248. b.pos.y = Math.random() * 5 + nest.pos.y;
  249. b.ownerPlayer = nest.owner;
  250. b.targetPlayer = -1;
  251. b.state = BugState.Chase;
  252. b.type = type;
  253. var itype = Type.enumIndex(type);
  254. b.speed = Settings.Gameplay.Bugs.Speed[itype];
  255. b.attack = Settings.Gameplay.Bugs.Attack[itype];
  256. b.health = b.getHP();
  257. nest.lastSpawnTime = time;
  258. addNewBug(b);
  259. }
  260. public function deleteNode(c:Cell)
  261. {
  262. // Unregister from cell
  263. var n = c.node;
  264. if (n == null)
  265. return;
  266. c.node = null;
  267. n.deleted = true;
  268. // Remove links
  269. var toDelete = new Array<Link>();
  270. for (l in links) {
  271. if (l.a == n || l.b == n) {
  272. toDelete.push(l);
  273. }
  274. }
  275. for (l in toDelete) {
  276. l.unlink();
  277. links.remove(l);
  278. linksLayer.removeChild(l.ui);
  279. }
  280. // Remove node
  281. nodes.remove(n);
  282. nodesLayer.removeChild(n.ui);
  283. }
  284. public function addNewNode(n:Node, ?direct:Bool=false)
  285. {
  286. // Add new links
  287. for (on in nodes)
  288. {
  289. if (canLinkWith(n, on))
  290. {
  291. var l = new Link();
  292. l.link(on, n);
  293. l.pending = true;
  294. addNewLink(l);
  295. }
  296. }
  297. if (direct)
  298. {
  299. n.state = NodeState.Idle;
  300. n.buildValue = n.getProperty("BuildCost");
  301. fixNodeCell(n);
  302. }
  303. else
  304. {
  305. n.state = NodeState.Creation;
  306. n.buildValue = 0;
  307. }
  308. // UI
  309. var ui = new NodeUI(n);
  310. ui.activate();
  311. n.ui = ui;
  312. nodesLayer.addChild(ui);
  313. nodes.push(n);
  314. }
  315. public function addNewLink(l:Link)
  316. {
  317. links.push(l);
  318. var ui = new LinkUI(l);
  319. l.ui = ui;
  320. ui.rebuildUI();
  321. linksLayer.addChild(ui);
  322. }
  323. public function addNewBug(b:Bug)
  324. {
  325. bugs.push(b);
  326. var ui = new BugUI(b);
  327. b.ui = ui;
  328. bugsLayer.addChild(ui);
  329. }
  330. public function removeBug(b:Bug)
  331. {
  332. bugs.remove(b);
  333. bugsLayer.removeChild(b.ui);
  334. }
  335. public function addNewPlayer(p:Player)
  336. {
  337. players.push(p);
  338. }
  339. public function serialize(s:Serializer)
  340. {
  341. var ws = new WorldState();
  342. ws.nodes = nodes;
  343. ws.links = links;
  344. ws.bugs = bugs;
  345. ws.players = players;
  346. ws.time = time;
  347. ws.properties = properties;
  348. ws.cells = new List<Cell>();
  349. for (c in grid.getCells())
  350. {
  351. ws.cells.add(c);
  352. }
  353. s.serialize(ws);
  354. }
  355. public function unSerialize(s:Unserializer)
  356. {
  357. var ws:WorldState = s.unserialize();
  358. reloadWorld(ws);
  359. }
  360. public function resetWorld()
  361. {
  362. properties = new WorldProperties();
  363. script = new script.Machine();
  364. nodes = new Array<Node>();
  365. links = new Array<Link>();
  366. bugs = new List<Bug>();
  367. time = 0;
  368. playing = false;
  369. cmdHandler = new WorldCommandHandler(this);
  370. // Grid
  371. grid = new Grid();
  372. if (gridLayer != null)
  373. removeChild(gridLayer);
  374. gridLayer = new GridUI(grid);
  375. addChild(gridLayer);
  376. gridLayer.rebuild(getWidth(), getHeight());
  377. grid.ui = gridLayer;
  378. // Root layer
  379. if (rootLayer != null)
  380. removeChild(rootLayer);
  381. rootLayer = new Sprite();
  382. addChild(rootLayer);
  383. linksLayer = new Sprite();
  384. rootLayer.addChild(linksLayer);
  385. linksFXLayer = new Sprite();
  386. rootLayer.addChild(linksFXLayer);
  387. nodesLayer = new Sprite();
  388. rootLayer.addChild(nodesLayer);
  389. if (Settings.Debug.Phero)
  390. nodesLayer.alpha = 0.2;
  391. bugsLayer = new Sprite();
  392. rootLayer.addChild(bugsLayer);
  393. effectsLayer = new Sprite();
  394. rootLayer.addChild(effectsLayer);
  395. if (hud != null) {
  396. hud.shutdown();
  397. removeChild(hud);
  398. }
  399. hud = new HUD();
  400. addChild(hud);
  401. tempNodeUI = null;
  402. }
  403. private function reloadWorld(ws:WorldState)
  404. {
  405. resetWorld();
  406. nodes = ws.nodes;
  407. links = ws.links;
  408. bugs = ws.bugs;
  409. time = ws.time;
  410. properties = ws.properties;
  411. for (n in nodes)
  412. {
  413. var ui = new NodeUI(n);
  414. ui.activate();
  415. n.ui = ui;
  416. nodesLayer.addChild(ui);
  417. }
  418. for (l in links)
  419. {
  420. var ui = new LinkUI(l);
  421. l.ui = ui;
  422. ui.rebuildUI();
  423. linksLayer.addChild(ui);
  424. }
  425. for (b in bugs)
  426. {
  427. var ui = new BugUI(b);
  428. b.ui = ui;
  429. bugsLayer.addChild(ui);
  430. }
  431. grid.loadCells(ws.cells.iterator());
  432. }
  433. public function loadLevel(l:Level)
  434. {
  435. level = l;
  436. nodes = level.nodes;
  437. links = level.links;
  438. grid.loadCells(level.cells.iterator());
  439. for (n in nodes)
  440. {
  441. fixNodeCell(n);
  442. postLoadNode(n);
  443. // UI
  444. var ui = new NodeUI(n);
  445. ui.activate();
  446. n.ui = ui;
  447. nodesLayer.addChild(ui);
  448. }
  449. for (l in links)
  450. {
  451. var ui = new LinkUI(l);
  452. l.ui = ui;
  453. ui.rebuildUI();
  454. linksLayer.addChild(ui);
  455. }
  456. }
  457. public function start()
  458. {
  459. playing = true;
  460. executeScriptlet("game_start");
  461. }
  462. function postLoadNode(n:Node)
  463. {
  464. if (n.type == NodeType.BugNest)
  465. {
  466. var bn = cast(n, BugNestNode);
  467. }
  468. }
  469. public function selectNode(n:Node)
  470. {
  471. if (selectedNode != null)
  472. {
  473. var ss = selectedNode;
  474. selectedNode = null;
  475. ss.ui.reset();
  476. }
  477. selectedNode = n;
  478. if (n != null)
  479. selectedNode.ui.reset();
  480. }
  481. public function getLinkConnection(a:Node, b:Node)
  482. {
  483. for (l in links) {
  484. if (l.a == a && l.b == b ||
  485. l.b == a && l.a == b) {
  486. return l;
  487. }
  488. }
  489. return null;
  490. }
  491. public function getNodeByID(id:String)
  492. {
  493. for (n in nodes)
  494. {
  495. if (n.id == id)
  496. return n;
  497. }
  498. return null;
  499. }
  500. static function getDist(na:Node, nb:Node):Float
  501. {
  502. return nb.pos.sub(na.pos).getSize();
  503. }
  504. function canLinkWith(na:Node, nb:Node):Bool
  505. {
  506. if (na.owner != nb.owner)
  507. return false;
  508. if (!nb.canGiveEnergy())
  509. return false;
  510. var dx = na.pos.x - nb.pos.x;
  511. var dy = na.pos.y - nb.pos.y;
  512. var rad = grid.HexSize * 3 + MathUtils.Epsilon;
  513. if (dx * dx + dy * dy <= rad * rad)
  514. {
  515. return true;
  516. }
  517. return false;
  518. }
  519. public function setMode(newMode:InputMode)
  520. {
  521. if (newMode == inputMode)
  522. return;
  523. if (newMode == InputMode.CreateNode)
  524. {
  525. rootLayer.mouseChildren = false;
  526. rootLayer.mouseEnabled = false;
  527. }
  528. else if (inputMode == InputMode.CreateNode)
  529. {
  530. nodesLayer.removeChild(tempNodeUI);
  531. tempNodeUI = null;
  532. tempNode = null;
  533. rootLayer.mouseEnabled = true;
  534. rootLayer.mouseChildren = true;
  535. var g = linksFXLayer.graphics;
  536. g.clear();
  537. }
  538. if (newMode == InputMode.Disabled)
  539. {
  540. rootLayer.mouseChildren = false;
  541. rootLayer.mouseEnabled = false;
  542. }
  543. else if (inputMode == InputMode.Disabled)
  544. {
  545. rootLayer.mouseEnabled = true;
  546. rootLayer.mouseChildren = true;
  547. }
  548. if (newMode == InputMode.LinkDirection) {
  549. }
  550. inputMode = newMode;
  551. }
  552. public function setCreationMode(nodeModel:Node)
  553. {
  554. setMode(InputMode.CreateNode);
  555. tempNode = nodeModel;
  556. if(tempNodeUI != null)
  557. nodesLayer.removeChild(tempNodeUI);
  558. tempNodeUI = new NodeUI(tempNode);
  559. tempNode.ui = tempNodeUI;
  560. nodesLayer.addChild(tempNodeUI);
  561. }
  562. function setType(node:Node, newtype:NodeType)
  563. {
  564. var s = node;
  565. if (s == null)
  566. return;
  567. s.type = newtype;
  568. s.ui.reset();
  569. }
  570. function fixNodeCell(n:Node)
  571. {
  572. var gc = grid.worldToCell(n.pos.x, n.pos.y);
  573. var bcell = grid.getCell(gc.i, gc.j);
  574. if (bcell == null)
  575. {
  576. bcell = new Cell();
  577. bcell.blocking = false;
  578. grid.setCell(gc.i, gc.j, bcell);
  579. }
  580. Debug.assert(bcell.node == null);
  581. bcell.node = n;
  582. bcell.setPhero(n.owner, Settings.Gameplay.MaxPhero);
  583. }
  584. public function executeScriptlet(id:String)
  585. {
  586. if (level != null)
  587. {
  588. var s = level.getScriptlet(id);
  589. if(s != null) {
  590. script.executeProgram(s.program, cmdHandler);
  591. }
  592. else {
  593. Log.trace("WARNING: scriptlet '" + id + "' does not exist");
  594. }
  595. }
  596. }
  597. public function onGameLose(reason:String)
  598. {
  599. if (Settings.Cheats.NeverLose)
  600. return;
  601. hud.showFailDialog(reason);
  602. }
  603. public function getRandomBugSpawner() : BugNestNode
  604. {
  605. var list = new Array<BugNestNode>();
  606. for (n in nodes) {
  607. if (n.type == NodeType.BugNest) {
  608. list.push(cast(n, BugNestNode));
  609. }
  610. }
  611. if (list.length > 0) {
  612. return list[ MathUtils.randomInt(list.length) ];
  613. }
  614. return null;
  615. }
  616. ////////////
  617. // Inputs //
  618. ////////////
  619. function keydown(e:KeyboardEvent)
  620. {
  621. var code = String.fromCharCode(e.charCode);
  622. var kcode = String.fromCharCode(e.keyCode);
  623. }
  624. function mouseDown(e:Event)
  625. {
  626. }
  627. function mouseUp(e:Event)
  628. {
  629. }
  630. function mouseMove(e:MouseEvent)
  631. {
  632. var gm = grid.snapToGrid(rootLayer.mouseX, rootLayer.mouseY);
  633. // Links FX
  634. {
  635. if (inputMode == InputMode.CreateNode)
  636. {
  637. var g = linksFXLayer.graphics;
  638. g.clear();
  639. tempNode.pos.x = gm.x;
  640. tempNode.pos.y = gm.y;
  641. tempNode.owner = activePlayerIndex;
  642. tempNodeUI.x = gm.x;
  643. tempNodeUI.y = gm.y;
  644. tempNodeUI.reset();
  645. g.lineStyle(1, 0xff0000, 1);
  646. for (on in nodes)
  647. {
  648. if (canLinkWith(tempNode, on))
  649. {
  650. g.moveTo(gm.x, gm.y);
  651. g.lineTo(on.pos.x, on.pos.y);
  652. }
  653. }
  654. }
  655. }
  656. }
  657. function mouseClicked(e:MouseEvent)
  658. {
  659. var gm = grid.snapToGrid(rootLayer.mouseX, rootLayer.mouseY);
  660. if (inputMode == InputMode.CreateNode)
  661. {
  662. var ll:Bool = false;
  663. for (on in nodes)
  664. {
  665. if (canLinkWith(tempNode, on))
  666. {
  667. ll = true;
  668. break;
  669. }
  670. }
  671. var nn:Node = Node.createNew(tempNode.type);
  672. nn.copyFrom(tempNode);
  673. nn.owner = activePlayerIndex;
  674. if (ll) {
  675. addNewNode(nn);
  676. }
  677. else if(KeyboardManager.Instance.getKeyState(Keyboard.SHIFT).active){
  678. addNewNode(nn, true);
  679. }
  680. }
  681. }
  682. ////////////////
  683. // Simulation //
  684. ////////////////
  685. static function getHighestConnectedNode(node:Node): Node
  686. {
  687. var res:Node = null;
  688. var maxe:Float = 0;
  689. for (n in node.neighbours)
  690. {
  691. if (n.type == NodeType.Core)
  692. return n;
  693. if (!n.deleted && n.energy > maxe) {
  694. res = n;
  695. maxe = n.energy;
  696. }
  697. }
  698. return res;
  699. }
  700. static function getRandomConnectedNode(node: Node): Node
  701. {
  702. if (node.neighbours.length == 0)
  703. return null;
  704. return node.neighbours[ MathUtils.randomInt(node.neighbours.length) ];
  705. }
  706. function stepBugs():Void
  707. {
  708. var bugsTargetCore:Bool = properties.targetCore;
  709. var nScouts = 0;
  710. // TODO OPTIM: static
  711. var toRemove = new List<Bug>();
  712. for (bug in bugs)
  713. {
  714. if (bug.health < 0)
  715. {
  716. toRemove.push(bug);
  717. continue;
  718. }
  719. if (bug.type == BugType.Scout)
  720. nScouts++;
  721. var nextCoords = grid.worldToCell(bug.pos.x, bug.pos.y);
  722. var newCell = grid.getCell(nextCoords.i, nextCoords.j);
  723. if (newCell == null)
  724. {
  725. // TODO: should never happen
  726. newCell = new Cell();
  727. newCell.blocking = false;
  728. grid.setCell(nextCoords.i, nextCoords.j, newCell);
  729. }
  730. var cellChanged = bug.currentCoords == null || !nextCoords.equals(bug.currentCoords);
  731. var destcoords = bug.targetCoords;
  732. var stateChanged:Bool = false;
  733. var findNextTarget:Bool = false;
  734. var doSearch = cellChanged || bug.mustReplan;
  735. bug.mustReplan = false;
  736. // First step only
  737. if (bug.currentCoords == null)
  738. bug.currentCoords = nextCoords;
  739. if (bug.state == BugState.Chase)
  740. {
  741. if (cellChanged)
  742. {
  743. // Drop phero on current cell
  744. if (bug.type == BugType.Scout)
  745. {
  746. var pherodelt = bug.ownerPhero * Settings.Gameplay.PheroDrop;
  747. bug.ownerPhero -= pherodelt;
  748. if (newCell.getPhero(bug.ownerPlayer) < bug.ownerPhero)
  749. {
  750. newCell.setPhero(bug.ownerPlayer, bug.ownerPhero);
  751. }
  752. }
  753. // Detect pheromones
  754. if (bug.targetPlayer == -1)
  755. {
  756. var pheroIndex = newCell.getHighestPhero(bug.ownerPlayer);
  757. if (pheroIndex >= 0)
  758. {
  759. bug.targetPlayer = pheroIndex;
  760. }
  761. }
  762. // Detect proximity nodes
  763. var tnode = grid.getCellNode(grid.worldToCell(bug.pos.x, bug.pos.y));
  764. if (tnode != null && !tnode.deleted && tnode.owner != bug.ownerPlayer)
  765. {
  766. if (bug.targetPlayer == -1)
  767. bug.targetPlayer = tnode.owner;
  768. if (tnode.type == NodeType.Core || !bugsTargetCore)
  769. {
  770. stateChanged = true;
  771. if (bug.type == BugType.Scout)
  772. {
  773. bug.state = BugState.Flee;
  774. // Mark cell
  775. bug.targetPhero = Settings.Gameplay.MaxPhero;
  776. newCell.setPhero(bug.targetPlayer, bug.targetPhero);
  777. }
  778. else if (bug.attack)
  779. {
  780. // Attack starting from nearest node
  781. bug.targetNode = tnode;
  782. bug.state = BugState.Attack;
  783. }
  784. // Search next step
  785. bug.mustReplan = true;
  786. doSearch = false;
  787. }
  788. }
  789. }
  790. if (doSearch)
  791. {
  792. // Find path
  793. if (bug.targetPlayer == -1)
  794. destcoords = grid.explorePheromonePath(nextCoords, bug.ownerPlayer);
  795. else
  796. {
  797. destcoords = grid.findPheromonePath(nextCoords, bug.ownerPlayer, bug.targetPlayer);
  798. // Correct current phero
  799. if (bug.type == BugType.Scout) {
  800. var destCell = grid.getCell(destcoords.i, destcoords.j);
  801. var targetPhero = destCell.getPhero(bug.targetPlayer);
  802. var currentPhero = newCell.getPhero(bug.targetPlayer);
  803. if (targetPhero < currentPhero)
  804. {
  805. newCell.setPhero(bug.targetPlayer, Math.max(targetPhero - MathUtils.Epsilon, 0));
  806. }
  807. }
  808. }
  809. }
  810. }
  811. else if (bug.state == BugState.Flee)
  812. {
  813. if (cellChanged)
  814. {
  815. var pherodelt = bug.targetPhero * Settings.Gameplay.PheroDrop;
  816. bug.targetPhero -= pherodelt;
  817. if (newCell.getPhero(bug.targetPlayer) < bug.targetPhero)
  818. {
  819. newCell.setPhero(bug.targetPlayer, bug.targetPhero);
  820. }
  821. var tnode = grid.getCellNode(grid.worldToCell(bug.pos.x, bug.pos.y));
  822. if (tnode != null && tnode.owner == bug.ownerPlayer)
  823. {
  824. bug.state = BugState.Chase;
  825. stateChanged = true;
  826. // Mark cell
  827. bug.ownerPhero = Settings.Gameplay.MaxPhero;
  828. newCell.setPhero(bug.ownerPlayer, bug.ownerPhero);
  829. // Search next step
  830. bug.mustReplan = true;
  831. doSearch = false;
  832. }
  833. }
  834. if (doSearch)
  835. {
  836. destcoords = grid.findPheromonePath(nextCoords, bug.targetPlayer, bug.ownerPlayer);
  837. // Correct current phero
  838. var destCell = grid.getCell(destcoords.i, destcoords.j);
  839. var targetPhero = destCell.getPhero(bug.ownerPlayer);
  840. if (targetPhero < newCell.getPhero(bug.ownerPlayer))
  841. {
  842. newCell.setPhero(bug.ownerPlayer, targetPhero - MathUtils.Epsilon);
  843. }
  844. }
  845. }
  846. else if (bug.state == BugState.Attack)
  847. {
  848. if (bug.targetNode.deleted) {
  849. bug.state = BugState.Chase;
  850. bug.mustReplan = true;
  851. }
  852. else if (doSearch)
  853. {
  854. var tnode = grid.getCellNode(grid.worldToCell(bug.pos.x, bug.pos.y));
  855. if (tnode != null && tnode == bug.targetNode)
  856. {
  857. var doCrunch = bug.getCrunchForce() > 0 &&
  858. (tnode.type != NodeType.Relay || Settings.Gameplay.AttackRelays);
  859. if (doCrunch) {
  860. bug.state = BugState.Crunch;
  861. bug.crunchLastTime = time;
  862. destcoords = null;
  863. }
  864. else {
  865. findNextTarget = true;
  866. }
  867. if (!StringUtils.isNullOrEmpty(tnode.onHitScript)) {
  868. executeScriptlet(tnode.onHitScript);
  869. }
  870. }
  871. }
  872. }
  873. else if (bug.state == BugState.Crunch)
  874. {
  875. var ccoords = grid.worldToCell(bug.pos.x, bug.pos.y);
  876. var tnode = grid.getCellNode(ccoords);
  877. if (tnode != null && !tnode.deleted)
  878. {
  879. if (bug.crunchLastTime + bug.getCrunchDt() < time) {
  880. bug.crunchLastTime = time;
  881. var cf = bug.getCrunchForce();
  882. tnode.health -= cf;
  883. if (tnode.health < 0)
  884. {
  885. tnode.health = 0;
  886. if (!Settings.Cheats.DoNotKillNodes) {
  887. deleteNode(grid.getCell(ccoords.i, ccoords.j));
  888. }
  889. else {
  890. tnode.deleted = true;
  891. }
  892. tnode.state = NodeState.Idle;
  893. findNextTarget = true;
  894. if (!StringUtils.isNullOrEmpty(tnode.onKilledScript)) {
  895. executeScriptlet(tnode.onKilledScript);
  896. }
  897. }
  898. }
  899. }
  900. else {
  901. findNextTarget = true;
  902. }
  903. }
  904. // Find next target
  905. if (findNextTarget)
  906. {
  907. var ccoords = grid.worldToCell(bug.pos.x, bug.pos.y);
  908. var tnode = grid.getCellNode(ccoords);
  909. var next:Node = null;
  910. if (tnode != null) {
  911. if (MathUtils.randomInt(2) == 0)
  912. next = getRandomConnectedNode(tnode);
  913. else
  914. next = getHighestConnectedNode(tnode);
  915. }
  916. if (next != null) {
  917. bug.state = BugState.Attack;
  918. bug.targetNode = next;
  919. destcoords = grid.worldToCell(next.pos.x, next.pos.y);
  920. }
  921. else {
  922. bug.state = BugState.Chase;
  923. bug.mustReplan = true;
  924. }
  925. }
  926. if ((bug.state == BugState.Chase || bug.state == BugState.Flee)
  927. && cellChanged && !stateChanged)
  928. {
  929. // Pick random cell
  930. if (destcoords == null || (MathUtils.randomInt(4) == 0)) {
  931. destcoords = grid.getValidRandomNeighbour(nextCoords, bug.currentCoords);
  932. }
  933. }
  934. bug.currentCoords = nextCoords;
  935. bug.targetCoords = destcoords;
  936. if (bug.ownerPhero < MathUtils.Epsilon)
  937. {
  938. bug.ownerPhero = 0;
  939. toRemove.push(bug);
  940. continue;
  941. }
  942. // Move it
  943. if (destcoords != null)
  944. {
  945. var wp = grid.cellToWorld(destcoords.i, destcoords.j);
  946. var delt = wp.sub(bug.pos).norm().scale(bug.speed);
  947. bug.pos.addIn(delt);
  948. bug.orientation = delt.getAtan();
  949. }
  950. else
  951. {
  952. // Not moving
  953. }
  954. }
  955. for (bug in toRemove)
  956. {
  957. removeBug(bug);
  958. }
  959. // Ajust scouts number !
  960. if (nScouts < properties.autoSpawnScouts)
  961. {
  962. var target = getRandomBugSpawner();
  963. if (target != null && (time - target.lastSpawnTime) > properties.autoSpawnInterval) {
  964. spawnBug(target, BugType.Scout);
  965. }
  966. }
  967. }
  968. // Update nodes
  969. function stepNodes()
  970. {
  971. var dt:Float = Settings.Gameplay.StepDuration;
  972. var totalConsumption = 0.0;
  973. var totalEnergy = 0.0;
  974. var anyActiveNode:Bool = false;
  975. for (node in nodes)
  976. {
  977. if (node.state == NodeState.Creation)
  978. continue;
  979. if (node.deleted)
  980. continue;
  981. if (node.owner == activePlayerIndex &&
  982. (!Settings.Gameplay.AttackRelays || node.type != NodeType.Relay))
  983. anyActiveNode = true;
  984. var searchResource = false;
  985. if (node.type == NodeType.Generator)
  986. {
  987. var de = node.getProperty("Rate") * dt;
  988. node.energy += de;
  989. var cap = node.getCapacity();
  990. if (node.energy > cap) {
  991. node.energy = cap;
  992. }
  993. }
  994. else if (node.type == NodeType.Relay)
  995. {
  996. searchResource = true;
  997. }
  998. else if (node.type == NodeType.Microwave)
  999. {
  1000. var ecost:Float = node.getProperty("Consumption") * dt;
  1001. var nhit:Float = node.getProperty("Force") * dt;
  1002. var nrange = node.getProperty("Range") * Settings.Gameplay.HexSize;
  1003. node.state = NodeState.Idle;
  1004. if (node.energy >= ecost)
  1005. {
  1006. for (bug in bugs)
  1007. {
  1008. if (MathUtils.isDistanceSmaller(bug.pos.x, bug.pos.y, node.pos.x, node.pos.y, nrange))
  1009. {
  1010. bug.health -= nhit;
  1011. node.state = NodeState.Attack;
  1012. }
  1013. }
  1014. }
  1015. if (node.state == NodeState.Attack)
  1016. {
  1017. node.energy -= ecost;
  1018. }
  1019. }
  1020. else if (node.type == NodeType.Ray)
  1021. {
  1022. var raynode = cast(node, RayNode);
  1023. var ecost:Float = node.getProperty("Consumption") * dt;
  1024. var nhit:Float = node.getProperty("Force") * dt;
  1025. var nrange = node.getProperty("Range") * Settings.Gameplay.HexSize;
  1026. var maxrays:Int = node.getPropertyInt("NumRays");
  1027. var nrays:Int = 0;
  1028. node.state = NodeState.Idle;
  1029. if (node.energy >= ecost)
  1030. {
  1031. raynode.target = null;
  1032. var highestHP:Float = 0;
  1033. for (bug in bugs) {
  1034. if (bug.type == BugType.Scout && !raynode.attackScouts)
  1035. continue;
  1036. if (MathUtils.isDistanceSmaller(bug.pos.x, bug.pos.y, node.pos.x, node.pos.y, nrange)) {
  1037. if (bug.getHP() > highestHP) {
  1038. node.state = NodeState.Attack;
  1039. raynode.target = bug;
  1040. highestHP = bug.getHP();
  1041. }
  1042. }
  1043. }
  1044. if (raynode.target != null) {
  1045. raynode.target.health -= nhit;
  1046. }
  1047. }
  1048. if(node.state == NodeState.Attack)
  1049. {
  1050. node.energy -= ecost;
  1051. }
  1052. }
  1053. else if (node.type == NodeType.Accumulator)
  1054. {
  1055. searchResource = true;
  1056. }
  1057. if (searchResource)
  1058. {
  1059. var nc = node.closestResCell;
  1060. if (nc == null || grid.getCell(nc.i, nc.j) == null)
  1061. {
  1062. nc = grid.findClosestEnergyCell(node.pos, grid.HexSize * 3);
  1063. node.closestResCell = nc;
  1064. }
  1065. if (nc != null)
  1066. {
  1067. var cell = grid.getCell(nc.i, nc.j);
  1068. // Consume
  1069. var delta = 30.0 * dt;
  1070. if (delta > cell.energy)
  1071. delta = cell.energy;
  1072. if (node.energy + delta > node.getCapacity())
  1073. delta = node.getCapacity() - node.energy;
  1074. if (delta > 0)
  1075. {
  1076. cell.energy -= delta;
  1077. node.energy += delta;
  1078. }
  1079. #if debug
  1080. if (Math.isNaN(cell.energy))
  1081. {
  1082. Log.trace("OMG");
  1083. }
  1084. #end
  1085. if (cell.energy < MathUtils.Epsilon)
  1086. {
  1087. cell.ui.rebuild();
  1088. node.closestResCell = null;
  1089. }
  1090. }
  1091. }
  1092. totalEnergy += node.energy;
  1093. }
  1094. if (level != null)
  1095. {
  1096. if (!anyActiveNode) {
  1097. onGameLose("All units killed lol");
  1098. }
  1099. }
  1100. }
  1101. }