├── hl.dx.hxml ├── hl.sdl.hxml ├── js.debug.hxml ├── hl.debug.hxml ├── js.hxml ├── hl.hxml ├── src ├── Direction.hx ├── ButtonType.hx ├── hashagon │ ├── Boot.hx │ ├── Convert.hx │ ├── Input.hx │ ├── displayobject │ │ ├── Tileset.hx │ │ ├── Image.hx │ │ ├── TextField.hx │ │ ├── Tile.hx │ │ └── Quad.hx │ ├── Sound.hx │ ├── Music.hx │ ├── Text.hx │ ├── Core.hx │ ├── Rectangle.hx │ ├── Geom.hx │ ├── Random.hx │ ├── Col.hx │ ├── Gfx.hx │ └── S.hx ├── engine │ ├── EntityType.hx │ ├── World.hx │ └── Entity.hx ├── scenes │ ├── GameOver.hx │ ├── LevelSelect.hx │ ├── TitleScreen.hx │ └── TowerDefence.hx ├── GameData.hx ├── Mouse.hx ├── Main.hx ├── QuickSave.hx ├── Scene.hx ├── WaveProgress.hx ├── UIPanel.hx ├── RealSimpleButton.hx ├── Waves.hx ├── cherry │ ├── res │ │ └── ManifestLoader.hx │ └── fs │ │ ├── ManifestBuilder.hx │ │ └── ManifestFileSystem.hx ├── SimpleButton.hx ├── TowerCursor.hx └── Game.hx ├── swf.hxml ├── data ├── pixelzim_0.png ├── pressstart_0.png ├── sounds │ ├── beam.mp3 │ ├── laser.mp3 │ ├── place.mp3 │ ├── sell.mp3 │ ├── button.mp3 │ ├── destroy.mp3 │ ├── upgrade.mp3 │ ├── vortex.mp3 │ ├── mine_land.mp3 │ ├── nextwave.mp3 │ ├── mine_launch.mp3 │ └── totaleclipse.mp3 ├── graphics │ ├── goal.png │ ├── heart.png │ ├── logo.png │ ├── enemies.png │ ├── towers.png │ ├── background.png │ ├── ld46tiles.png │ ├── particles.png │ ├── nextindicate.png │ ├── gameoverscreen.png │ ├── beam_vertical_down.png │ ├── beam_vertical_up.png │ ├── beam_horizontal_left.png │ └── beam_horizontal_right.png ├── text │ ├── stage2.csv │ ├── stage1.csv │ └── variables.json └── pixelzim.fnt ├── base.hxml ├── README.md ├── .vscode ├── settings.json ├── commandbar.json └── tasks.json ├── tcgames.css ├── index.html └── .gitignore /hl.dx.hxml: -------------------------------------------------------------------------------- 1 | hl.hxml 2 | -lib hldx -------------------------------------------------------------------------------- /hl.sdl.hxml: -------------------------------------------------------------------------------- 1 | hl.hxml 2 | -lib hlsdl -------------------------------------------------------------------------------- /js.debug.hxml: -------------------------------------------------------------------------------- 1 | js.hxml 2 | -debug -------------------------------------------------------------------------------- /hl.debug.hxml: -------------------------------------------------------------------------------- 1 | hl.dx.hxml 2 | -debug -------------------------------------------------------------------------------- /js.hxml: -------------------------------------------------------------------------------- 1 | base.hxml 2 | -js bin/html5/game.js 3 | -------------------------------------------------------------------------------- /hl.hxml: -------------------------------------------------------------------------------- 1 | base.hxml 2 | -D windowSize=2000x1200 3 | -hl bin/hashlink/game.hl 4 | -------------------------------------------------------------------------------- /src/Direction.hx: -------------------------------------------------------------------------------- 1 | enum Direction{ 2 | UP; 3 | DOWN; 4 | LEFT; 5 | RIGHT; 6 | } 7 | -------------------------------------------------------------------------------- /swf.hxml: -------------------------------------------------------------------------------- 1 | base.hxml 2 | -swf bin/flash/game.swf 3 | -swf-header 1280:720:60:000000 4 | -swf-version 24.0 5 | -------------------------------------------------------------------------------- /src/ButtonType.hx: -------------------------------------------------------------------------------- 1 | enum ButtonType{ 2 | LASER; 3 | BEAM; 4 | VORTEX; 5 | SHOOTY; 6 | UPGRADE; 7 | SELL; 8 | } -------------------------------------------------------------------------------- /data/pixelzim_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/pixelzim_0.png -------------------------------------------------------------------------------- /data/pressstart_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/pressstart_0.png -------------------------------------------------------------------------------- /data/sounds/beam.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/beam.mp3 -------------------------------------------------------------------------------- /data/sounds/laser.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/laser.mp3 -------------------------------------------------------------------------------- /data/sounds/place.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/place.mp3 -------------------------------------------------------------------------------- /data/sounds/sell.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/sell.mp3 -------------------------------------------------------------------------------- /data/graphics/goal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/goal.png -------------------------------------------------------------------------------- /data/graphics/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/heart.png -------------------------------------------------------------------------------- /data/graphics/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/logo.png -------------------------------------------------------------------------------- /data/sounds/button.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/button.mp3 -------------------------------------------------------------------------------- /data/sounds/destroy.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/destroy.mp3 -------------------------------------------------------------------------------- /data/sounds/upgrade.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/upgrade.mp3 -------------------------------------------------------------------------------- /data/sounds/vortex.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/vortex.mp3 -------------------------------------------------------------------------------- /data/graphics/enemies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/enemies.png -------------------------------------------------------------------------------- /data/graphics/towers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/towers.png -------------------------------------------------------------------------------- /data/sounds/mine_land.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/mine_land.mp3 -------------------------------------------------------------------------------- /data/sounds/nextwave.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/nextwave.mp3 -------------------------------------------------------------------------------- /data/graphics/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/background.png -------------------------------------------------------------------------------- /data/graphics/ld46tiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/ld46tiles.png -------------------------------------------------------------------------------- /data/graphics/particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/particles.png -------------------------------------------------------------------------------- /data/sounds/mine_launch.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/mine_launch.mp3 -------------------------------------------------------------------------------- /data/sounds/totaleclipse.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/sounds/totaleclipse.mp3 -------------------------------------------------------------------------------- /data/graphics/nextindicate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/nextindicate.png -------------------------------------------------------------------------------- /base.hxml: -------------------------------------------------------------------------------- 1 | -cp src 2 | -lib heaps 3 | -lib actuate 4 | -D resourcesPath=data 5 | -D heaps_enable_hl_mp3 6 | -main hashagon.Boot 7 | -dce full -------------------------------------------------------------------------------- /data/graphics/gameoverscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/gameoverscreen.png -------------------------------------------------------------------------------- /data/graphics/beam_vertical_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/beam_vertical_down.png -------------------------------------------------------------------------------- /data/graphics/beam_vertical_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/beam_vertical_up.png -------------------------------------------------------------------------------- /data/graphics/beam_horizontal_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/beam_horizontal_left.png -------------------------------------------------------------------------------- /data/graphics/beam_horizontal_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryCavanagh/tower-defence-of-the-heart/HEAD/data/graphics/beam_horizontal_right.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tower Defence of the Heart 2 | 3 | This was my jam entry for Ludum Dare 46, which happened April 2020. 4 | 5 | Stay tuned for a post compo version that fixes a bunch of stuff 6 | -------------------------------------------------------------------------------- /src/hashagon/Boot.hx: -------------------------------------------------------------------------------- 1 | package hashagon; 2 | 3 | class Boot extends hxd.App { 4 | override function init() { 5 | } 6 | 7 | static function main() { 8 | new Core(); 9 | } 10 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "titleBar.inactiveBackground": "#494949", 4 | "titleBar.activeBackground": "#6d6d6d", 5 | "titleBar.activeForeground": "#ffffff" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/engine/EntityType.hx: -------------------------------------------------------------------------------- 1 | package engine; 2 | 3 | enum EntityType{ 4 | ENEMY; //All enemies are exactly the same: variables are changed in the waves 5 | 6 | TOWER_SHOOTY; 7 | TOWER_BEAM; 8 | TOWER_VORTEX; 9 | TOWER_LASER; 10 | 11 | BULLET; 12 | BEAM; 13 | VORTEX; 14 | LASER; 15 | 16 | GOAL; 17 | } -------------------------------------------------------------------------------- /src/scenes/GameOver.hx: -------------------------------------------------------------------------------- 1 | package scenes; 2 | 3 | import hashagon.*; 4 | import engine.*; 5 | import hxd.Key; 6 | 7 | @:keep 8 | class GameOver{ 9 | public static function init(){ 10 | gameoverscreen = new h2d.Anim([Gfx.getimage("gameoverscreen")], 0); 11 | Gfx.core.s2d.addChild(gameoverscreen); 12 | Sound.play("nextwave"); 13 | } 14 | 15 | public static function update() { 16 | } 17 | 18 | public static function cleanup(){ 19 | } 20 | 21 | public static var gameoverscreen:h2d.Anim; 22 | } -------------------------------------------------------------------------------- /src/hashagon/Convert.hx: -------------------------------------------------------------------------------- 1 | package haxegon; 2 | 3 | class Convert { 4 | public static function tostring(?value:Dynamic):String { 5 | return Std.string(value); 6 | } 7 | 8 | public static function toint(?value:Dynamic):Int { 9 | if (Std.is(value, Int)){ 10 | return value; 11 | }else if(Std.is(value, Float)){ 12 | return Std.int(value); 13 | } 14 | return Std.parseInt(Std.string(value)); 15 | } 16 | 17 | public static function tofloat(?value:Dynamic):Float { 18 | return Std.parseFloat(value); 19 | } 20 | } -------------------------------------------------------------------------------- /src/hashagon/Input.hx: -------------------------------------------------------------------------------- 1 | package hashagon; 2 | 3 | import hxd.Key; 4 | 5 | class Input{ 6 | public static function pressed(k:Int):Bool { 7 | if (Key.isDown(k)) { 8 | return true; 9 | } 10 | return false; 11 | } 12 | 13 | public static function justpressed(k:Int):Bool { 14 | if (Key.isPressed(k)) { 15 | return true; 16 | } 17 | return false; 18 | } 19 | 20 | public static function justreleased(k:Int):Bool { 21 | if (Key.isReleased(k)) { 22 | return true; 23 | } 24 | return false; 25 | } 26 | } -------------------------------------------------------------------------------- /tcgames.css: -------------------------------------------------------------------------------- 1 | body 2 | { 3 | color:#CCCCCC; 4 | background-color:#000000; 5 | } 6 | 7 | body, p, h1, h2, h3, table, td, th, ul, ol, textarea, input 8 | { 9 | font-family: helvetica,verdana,arial,sans-serif; 10 | } 11 | 12 | a:link {color: #444444; text-decoration: underlined; font-weight:normal;} 13 | a:visited {color: #444444; text-decoration: none; font-weight:normal;} 14 | a:hover {color: #888888; text-decoration: underlined; font-weight:normal;} 15 | a:active {color: #888888; text-decoration: underlined; font-weight:normal;} -------------------------------------------------------------------------------- /src/GameData.hx: -------------------------------------------------------------------------------- 1 | import haxe.Json; 2 | import hashagon.*; 3 | 4 | class GameData{ 5 | public static var towers:Dynamic; 6 | public static var waves:Dynamic; 7 | public static var other:Dynamic; 8 | public static var jfile:Dynamic; 9 | public static var currentstage:Int; 10 | 11 | public static function init(){ 12 | jfile = Json.parse(hxd.Res.text.variables.entry.getText()); 13 | 14 | towers = jfile.towers; 15 | } 16 | 17 | public static function loadlevel(lvl:Int){ 18 | if(lvl == 1){ 19 | waves = jfile.waves_stage1; 20 | other = jfile.other_stage1; 21 | }else if(lvl == 2){ 22 | waves = jfile.waves_stage2; 23 | other = jfile.other_stage2; 24 | } 25 | currentstage = lvl; 26 | } 27 | } -------------------------------------------------------------------------------- /src/Mouse.hx: -------------------------------------------------------------------------------- 1 | import hashagon.*; 2 | import hxd.Key; 3 | 4 | class Mouse{ 5 | public static var x(get, null):Float; 6 | public static function get_x(){ 7 | return Gfx.core.s2d.mouseX; 8 | } 9 | 10 | public static var y(get, null):Float; 11 | public static function get_y(){ 12 | return Gfx.core.s2d.mouseY; 13 | } 14 | 15 | public static function leftheld():Bool { 16 | if (Key.isDown(Key.MOUSE_LEFT)) { 17 | return true; 18 | } 19 | return false; 20 | } 21 | 22 | public static function leftclick():Bool { 23 | if (Key.isPressed(Key.MOUSE_LEFT)) { 24 | return true; 25 | } 26 | return false; 27 | } 28 | 29 | public static function leftreleased():Bool { 30 | if (Key.isReleased(Key.MOUSE_LEFT)) { 31 | return true; 32 | } 33 | return false; 34 | } 35 | } -------------------------------------------------------------------------------- /src/hashagon/displayobject/Tileset.hx: -------------------------------------------------------------------------------- 1 | package hashagon.displayobject; 2 | 3 | class Tileset{ 4 | public function new(tilesetname:String, w:Int, h:Int){ 5 | tiles = []; 6 | 7 | tilesetdata = hxd.Res.load("graphics/" + tilesetname + ".png").toTile(); 8 | tiles = tilesetdata.gridFlatten(w); 9 | 10 | name = tilesetname; 11 | width = w; height = h; 12 | numtiles = tiles.length; 13 | } 14 | 15 | public function pivot(v:Int){ 16 | //if you call this you prob just want to center shrug emoji 17 | for(t in tiles){ 18 | t.center(); 19 | } 20 | } 21 | 22 | public var name:String; 23 | public var width:Int; 24 | public var height:Int; 25 | public var numtiles:Int; 26 | 27 | public var tilesetdata:h2d.Tile; 28 | public var tiles:Array; 29 | 30 | public var halign:Int; 31 | public var valign:Int; 32 | } -------------------------------------------------------------------------------- /src/hashagon/Sound.hx: -------------------------------------------------------------------------------- 1 | package hashagon; 2 | 3 | //Very lightweight for Ludum Dare 4 | class Sound{ 5 | public static function init(){ 6 | loadedsounds = new Map(); 7 | } 8 | 9 | public static function load(soundfile:String){ 10 | if(!loadedsounds.exists(soundfile)){ 11 | loadedsounds.set(soundfile, hxd.Res.load("sounds/" + soundfile + ".mp3").toSound()); 12 | } 13 | } 14 | 15 | public static function play(soundfile:String){ 16 | var soundresource:hxd.res.Sound; 17 | 18 | if(loadedsounds.exists(soundfile)){ 19 | soundresource = loadedsounds.get(soundfile); 20 | }else{ 21 | soundresource = hxd.Res.load("sounds/" + soundfile + ".mp3").toSound(); 22 | loadedsounds.set(soundfile, soundresource); 23 | } 24 | 25 | soundresource.play(); 26 | } 27 | 28 | public static var loadedsounds:Map; 29 | } -------------------------------------------------------------------------------- /data/text/stage2.csv: -------------------------------------------------------------------------------- 1 | 7,7,7,7,38,7,7,44,41,38,7,7,7,38,7,41,48,41,12,12,12,12,12,12, 2 | 7,39,7,7,7,7,37,7,7,7,7,50,7,7,7,7,47,41,12,12,12,12,12,12, 3 | 7,42,7,3,1,1,1,1,1,1,1,1,1,1,1,4,7,41,12,12,12,12,12,12, 4 | 7,7,7,2,7,7,7,38,7,7,7,7,7,35,7,2,38,41,12,12,12,12,12,12, 5 | 1,4,7,2,40,46,7,7,7,7,7,7,7,33,7,2,7,41,12,12,12,12,12,12, 6 | 7,2,38,2,7,7,7,12,12,12,7,7,7,7,7,2,7,41,12,12,12,12,12,12, 7 | 37,2,7,5,1,1,1,12,12,12,1,1,1,4,7,2,7,41,12,12,12,12,12,12, 8 | 7,2,7,7,7,7,7,12,12,12,7,48,7,2,7,2,7,7,12,12,12,12,12,12, 9 | 7,2,7,36,7,7,7,7,7,7,7,47,7,2,7,5,1,1,1,12,12,12,12,12, 10 | 7,2,7,7,7,11,7,7,7,7,7,7,7,2,7,7,7,7,12,12,12,12,12,12, 11 | 7,5,1,1,1,1,1,1,1,1,1,1,1,6,7,7,7,41,12,12,12,12,12,12, 12 | 7,37,7,7,7,7,7,7,7,7,7,7,38,7,7,7,7,41,12,12,12,12,12,12, 13 | 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, 14 | 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12 -------------------------------------------------------------------------------- /src/Main.hx: -------------------------------------------------------------------------------- 1 | import h2d.Bitmap; 2 | import hashagon.*; 3 | import hxd.Key; 4 | 5 | @:keep 6 | class Main{ 7 | public static function init(){ 8 | Game.loadfonts(); 9 | Gfx.loadimage("background"); 10 | Gfx.loadimage("logo"); 11 | Gfx.loadimage("gameoverscreen"); 12 | Gfx.loadimage("beam_horizontal_left"); 13 | Gfx.loadimage("beam_horizontal_right"); 14 | Gfx.loadimage("beam_vertical_up"); 15 | Gfx.loadimage("beam_vertical_down"); 16 | Gfx.loadtiles("enemies", 10, 10); 17 | Gfx.loadtiles("towers", 10, 10); 18 | Gfx.loadtiles("particles", 10, 10); 19 | Gfx.loadtiles("nextindicate", 20, 20); 20 | Gfx.loadtiles("heart", 30, 30); 21 | Gfx.gettileset("particles").pivot(Text.CENTER); 22 | Gfx.loadtiles("goal", 30, 30); 23 | 24 | GameData.init(); 25 | 26 | Scene.change("titlescreen"); 27 | } 28 | 29 | public static function update() { 30 | } 31 | 32 | public static function cleanup(){ 33 | } 34 | } -------------------------------------------------------------------------------- /data/text/stage1.csv: -------------------------------------------------------------------------------- 1 | 17,18,7,2,7,26,28,17,17,26,26,17,19,27,27,19,19,28,12,12,12,12,12,12, 2 | 18,19,9,2,7,27,26,18,19,27,27,19,7,7,7,7,7,28,12,12,12,12,12,12, 3 | 19,7,7,2,10,7,27,19,7,7,7,7,7,3,1,4,7,28,12,12,12,12,12,12, 4 | 7,7,7,2,7,7,7,7,7,12,12,12,7,2,7,2,7,28,12,12,12,12,12,12, 5 | 1,1,1,1,1,1,4,7,10,12,12,12,1,6,7,2,7,28,12,12,12,12,12,12, 6 | 7,7,7,7,8,7,2,7,7,12,12,12,7,7,7,2,7,28,12,12,12,12,12,12, 7 | 20,7,9,7,7,7,2,7,7,7,7,7,9,7,7,2,7,28,12,12,12,12,12,12, 8 | 20,29,7,3,1,1,1,1,1,1,1,4,7,7,7,2,10,28,12,12,12,12,12,12, 9 | 20,28,7,2,7,7,7,7,7,7,8,2,7,7,7,2,7,28,12,12,12,12,12,12, 10 | 20,28,7,5,1,1,1,7,17,10,7,5,1,1,1,6,7,28,12,12,12,12,12,12, 11 | 20,28,20,7,7,7,2,7,19,7,7,10,7,7,7,7,7,28,12,12,12,12,12,12, 12 | 20,28,17,17,8,7,2,7,7,7,17,7,17,7,7,17,17,28,12,12,12,12,12,12, 13 | 12,12,12,12,12,12,2,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, 14 | 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12 -------------------------------------------------------------------------------- /src/hashagon/displayobject/Image.hx: -------------------------------------------------------------------------------- 1 | package hashagon.displayobject; 2 | 3 | class Image{ 4 | public function new(){ 5 | bitmap = null; 6 | attached = false; 7 | disposed = false; 8 | } 9 | 10 | public function drawimage(x:Float, y:Float, imagename:String){ 11 | if (!Gfx.imageindex.exists(imagename)) { 12 | Gfx.loadimage(imagename); 13 | //throw("Error: \"" + imagename + "\" is not loaded."); 14 | } 15 | 16 | if(bitmap == null){ 17 | bitmap = new h2d.Bitmap(Gfx.imageindex.get(imagename)); 18 | } 19 | 20 | bitmap.x = x; 21 | bitmap.y = y; 22 | 23 | if(!attached){ 24 | Gfx.core.s2d.addChild(bitmap); 25 | attached = true; 26 | }else{ 27 | Gfx.core.s2d.removeChild(bitmap); 28 | Gfx.core.s2d.addChild(bitmap); 29 | } 30 | } 31 | 32 | public function dispose(){ 33 | disposed = true; 34 | Gfx.core.s2d.removeChild(bitmap); 35 | bitmap = null; 36 | } 37 | 38 | public var bitmap:h2d.Bitmap; 39 | public var attached:Bool; 40 | public var disposed:Bool; 41 | } -------------------------------------------------------------------------------- /.vscode/commandbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "skipTerminateQuickPick": true, 3 | "skipSwitchToOutput": false, 4 | "skipErrorMessage": true, 5 | "commands": [ 6 | { 7 | "text": "🚀 Build HL", 8 | "color": "yellow", 9 | "commandType":"palette", 10 | "command": "workbench.action.tasks.runTask|HL release", 11 | "alignment": "right", 12 | "skipTerminateQuickPick": false, 13 | "priority": -10 14 | }, 15 | { 16 | "text": "Run HL", 17 | "color": "yellow", 18 | "command": "hl bin/hashlink/game.hl", 19 | "alignment": "right", 20 | "skipTerminateQuickPick": false, 21 | "priority": -11 22 | }, 23 | { 24 | "text": "🌍 Build JS", 25 | "color": "cyan", 26 | "commandType":"palette", 27 | "command": "workbench.action.tasks.runTask|JS release", 28 | "alignment": "right", 29 | "skipTerminateQuickPick": false, 30 | "priority": -20 31 | }, 32 | { 33 | "text": "Run JS", 34 | "color": "cyan", 35 | "command": "start firefox index.html", 36 | "webRoot": "${workspaceFolder}", 37 | "alignment": "right", 38 | "skipTerminateQuickPick": false, 39 | "priority": -21 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /src/hashagon/displayobject/TextField.hx: -------------------------------------------------------------------------------- 1 | package hashagon.displayobject; 2 | 3 | class TextField{ 4 | public function new(){ 5 | //var font:h2d.Font = hxd.Res.pixel.toFont(); 6 | tf = new h2d.Text(Game.textfont); 7 | disposed = false; 8 | } 9 | 10 | public function update(x:Float, y:Float, text:Dynamic, color:Int = 0xFFFFFF, alpha:Float = 1.0){ 11 | if(disposed) return; 12 | 13 | if (Std.is(text, Array)){ 14 | text = text.toString(); 15 | }else if (!Std.is(text, String)){ 16 | text = Std.string(text); 17 | } 18 | 19 | tf.x = x; 20 | tf.y = y; 21 | tf.text = text; 22 | tf.textColor = color; 23 | tf.alpha = alpha; 24 | tf.setScale(Text.size); 25 | if(Text.align == Text.LEFT) tf.textAlign = Left; 26 | if(Text.align == Text.CENTER) tf.textAlign = Center; 27 | if(Text.align == Text.RIGHT) tf.textAlign = Right; 28 | } 29 | 30 | public function display(){ 31 | if(disposed) return; 32 | Text.core.s2d.addChild(tf); 33 | } 34 | 35 | public function dispose(){ 36 | disposed = true; 37 | Text.core.s2d.removeChild(tf); 38 | tf = null; 39 | } 40 | 41 | public var tf:h2d.Text; 42 | public var disposed:Bool; 43 | } -------------------------------------------------------------------------------- /src/hashagon/Music.hx: -------------------------------------------------------------------------------- 1 | package hashagon; 2 | 3 | //Very lightweight for Ludum Dare 4 | class Music{ 5 | public static function init(){ 6 | loadedsounds = new Map(); 7 | currentlyplaying = ""; 8 | } 9 | 10 | public static function load(soundfile:String){ 11 | if(!loadedsounds.exists(soundfile)){ 12 | loadedsounds.set(soundfile, hxd.Res.load("sounds/" + soundfile + ".mp3").toSound()); 13 | } 14 | } 15 | 16 | public static function play(soundfile:String){ 17 | var soundresource:hxd.res.Sound; 18 | currentlyplaying = soundfile; 19 | 20 | if(loadedsounds.exists(soundfile)){ 21 | soundresource = loadedsounds.get(soundfile); 22 | }else{ 23 | soundresource = hxd.Res.load("sounds/" + soundfile + ".mp3").toSound(); 24 | loadedsounds.set(soundfile, soundresource); 25 | } 26 | 27 | soundresource.play(true); 28 | } 29 | 30 | public static function stop(){ 31 | if(currentlyplaying == "") return; 32 | 33 | var soundresource:hxd.res.Sound = loadedsounds.get(currentlyplaying); 34 | soundresource.stop(); 35 | currentlyplaying = ""; 36 | } 37 | 38 | public static var loadedsounds:Map; 39 | public static var currentlyplaying:String; 40 | } -------------------------------------------------------------------------------- /src/hashagon/displayobject/Tile.hx: -------------------------------------------------------------------------------- 1 | package hashagon.displayobject; 2 | 3 | class Tile{ 4 | public function new(){ 5 | attached = false; 6 | tilesetattached = false; 7 | currenttileset = null; 8 | disposed = false; 9 | } 10 | 11 | public function drawtile(x:Float, y:Float, tilesetname:String, tilenum:Int){ 12 | if(disposed) return; 13 | //No bounds checking for ludum dare! 14 | if(!tilesetattached){ 15 | currenttileset = Gfx.tileindex.get(tilesetname); 16 | currentanim = new h2d.Anim(currenttileset.tiles, 0); 17 | tilesetattached = true; 18 | } 19 | 20 | currentanim.x = x; 21 | currentanim.y = y; 22 | currentanim.currentFrame = tilenum; 23 | 24 | if(!attached){ 25 | Gfx.core.s2d.addChild(currentanim); 26 | attached = true; 27 | } 28 | } 29 | 30 | public function reattach(){ 31 | if(!attached){ 32 | Gfx.core.s2d.addChild(currentanim); 33 | attached = true; 34 | }else{ 35 | Gfx.core.s2d.removeChild(currentanim); 36 | Gfx.core.s2d.addChild(currentanim); 37 | } 38 | } 39 | 40 | public function dispose(){ 41 | disposed = true; 42 | Gfx.core.s2d.removeChild(currentanim); 43 | currentanim = null; 44 | } 45 | 46 | public var currentanim:h2d.Anim; 47 | public var currenttileset:Tileset; 48 | public var tilesetattached:Bool; 49 | public var attached:Bool; 50 | public var disposed:Bool; 51 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Untitled - Terry Cavanagh Games 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 |

untitled game by terry cavanagh, made for ld46

12 |
13 |
14 | 15 | 25 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/hashagon/Text.hx: -------------------------------------------------------------------------------- 1 | package hashagon; 2 | 3 | import hashagon.displayobject.*; 4 | 5 | class Text{ 6 | public static function init(){ 7 | textfieldmap = new Map(); 8 | Text.align = Text.LEFT; 9 | Text.size = 1; 10 | } 11 | 12 | public static function display(x:Float, y:Float, text:Dynamic, color:Int = 0xFFFFFF, alpha:Float = 1.0, id:String = "", ?pos:haxe.PosInfos) { 13 | if(id == "") id = pos.fileName + "_" + pos.lineNumber; 14 | 15 | var tf:TextField; 16 | if (textfieldmap.exists(id)){ 17 | tf = textfieldmap.get(id); 18 | tf.update(x, y, text, color, alpha); 19 | //core.s2d.removeChild(tf.tf); 20 | }else{ 21 | tf = new TextField(); 22 | textfieldmap.set(id, tf); 23 | tf.update(x, y, text, color, alpha); 24 | } 25 | 26 | tf.display(); 27 | } 28 | 29 | public static function cleanup(){ 30 | if(textfieldmap != null){ 31 | for (k in textfieldmap.keys()){ 32 | textfieldmap.get(k).dispose(); 33 | textfieldmap.remove(k); 34 | } 35 | } 36 | } 37 | 38 | public static var size:Float; 39 | 40 | public static var core:Core; 41 | private static var textfieldmap:Map; 42 | 43 | public static var LEFT:Int = 0; 44 | public static var TOP:Int = 0; 45 | public static var CENTER:Int = -200000; 46 | public static var RIGHT:Int = -300000; 47 | public static var BOTTOM:Int = -300000; 48 | 49 | public static var align:Int = 0; 50 | } -------------------------------------------------------------------------------- /src/scenes/LevelSelect.hx: -------------------------------------------------------------------------------- 1 | package scenes; 2 | 3 | import hashagon.*; 4 | import engine.*; 5 | import hxd.Key; 6 | 7 | class LevelSelect{ 8 | public static function init(){ 9 | titlescreen = new h2d.Anim([Gfx.getimage("background")], 0); 10 | Gfx.core.s2d.addChild(titlescreen); 11 | 12 | selectastage = new h2d.Text(Game.smallfont, Gfx.core.s2d); 13 | selectastage.x = Gfx.screenwidthmid; 14 | selectastage.y = 30 - 3; 15 | selectastage.textAlign = Center; 16 | selectastage.text = "SELECT A STAGE"; 17 | 18 | stage1button = new RealSimpleButton("FalliNg iN Love", 50 - 3, () -> { 19 | stage1button.remove(); 20 | stage2button.remove(); 21 | selectastage.remove(); 22 | titlescreen.remove(); 23 | 24 | Music.play("totaleclipse"); 25 | GameData.loadlevel(1); 26 | Scene.change("towerdefence"); 27 | }, Gfx.core.s2d); 28 | stage2button = new RealSimpleButton("FalliNg apart", 70 - 3,() -> { 29 | stage1button.remove(); 30 | stage2button.remove(); 31 | selectastage.remove(); 32 | titlescreen.remove(); 33 | 34 | Music.play("totaleclipse"); 35 | GameData.loadlevel(2); 36 | Scene.change("towerdefence"); 37 | }, Gfx.core.s2d); 38 | } 39 | 40 | public static function update() { 41 | } 42 | 43 | public static function cleanup(){ 44 | } 45 | 46 | public static var titlescreen:h2d.Anim; 47 | public static var selectastage:h2d.Text; 48 | 49 | public static var stage1button:RealSimpleButton; 50 | public static var stage2button:RealSimpleButton; 51 | } -------------------------------------------------------------------------------- /src/hashagon/Core.hx: -------------------------------------------------------------------------------- 1 | package hashagon; 2 | 3 | import haxe.Constraints.Function; 4 | import haxe.Timer; 5 | 6 | @:access(Scene) 7 | class Core extends hxd.App { 8 | override function init() { 9 | super.init(); 10 | Gfx.core = this; 11 | Text.core = this; 12 | assetsloaded = false; 13 | 14 | #if manifestfs 15 | var loader:cherry.res.ManifestLoader = cherry.fs.ManifestBuilder.initManifest("data"); 16 | cherry.res.ManifestLoader.concurrentFiles = 4; 17 | var preloader = new TerryManifestProgress(loader, Col.BLACK, completeinit, s2d); 18 | preloader.start(); 19 | #else 20 | hxd.Res.initEmbed(); 21 | completeinit(); 22 | #end 23 | } 24 | 25 | function completeinit(){ 26 | //s2d.scaleMode = Stretch(1280, 720); 27 | hxd.Res.initEmbed(); 28 | //s2d.scaleMode = LetterBox(320, 180, false); 29 | //s2d.scaleMode = LetterBox(240, 135, false); 30 | s2d.scaleMode = LetterBox(200, 120, false); 31 | 32 | Text.init(); 33 | Gfx.init(); 34 | Sound.init(); 35 | Music.init(); 36 | Random.seed = 0; 37 | 38 | deltatime = 0; 39 | Scene.init(); 40 | assetsloaded = true; 41 | } 42 | 43 | // on each frame 44 | override function update(dt:Float) { 45 | //if (Gfx.clearcolor != Col.TRANSPARENT) Gfx.clearscreen(Gfx.clearcolor); 46 | deltatime = dt; 47 | if(assetsloaded) Scene.update(); 48 | } 49 | 50 | public static function delaycall(f:Function, time:Float) { 51 | Timer.delay(function() { f(); }, Std.int(time * 1000)); 52 | } 53 | 54 | public static function cleanup(){ 55 | Text.cleanup(); 56 | Gfx.cleanup(); 57 | } 58 | 59 | @:generic 60 | public static function create2darray(width:Int, height:Int, value:T):Array> { 61 | return [for (x in 0 ... width) [for (y in 0 ... height) value]]; 62 | } 63 | 64 | public static var deltatime:Float; 65 | public static var assetsloaded:Bool; 66 | } -------------------------------------------------------------------------------- /src/QuickSave.hx: -------------------------------------------------------------------------------- 1 | import engine.*; 2 | import hashagon.*; 3 | 4 | class QuickSave{ 5 | public static function quicksave(world:World){ 6 | var savestring:String = ""; 7 | savestring += Waves.currentwave + ","; 8 | savestring += Game.gold + ","; 9 | savestring += (world.towers.length - 1) + ","; 10 | for(tower in world.towers){ 11 | if(tower.type != EntityType.GOAL){ 12 | savestring += world.gridx(tower.x) + ","; 13 | savestring += world.gridy(tower.y) + ","; 14 | savestring += tower.type + ","; 15 | savestring += tower.level + ","; 16 | } 17 | } 18 | trace(savestring); 19 | } 20 | 21 | public static function quickload(world:World){ 22 | var loadstring:String = 23 | "18,2000,0"; 24 | 25 | for(monster in world.monsters){ 26 | monster.destroy(); 27 | } 28 | 29 | for(tower in world.towers){ 30 | tower.destroy(); 31 | } 32 | 33 | var v:Array = loadstring.split(","); 34 | v.reverse(); 35 | Waves.currentwave = Std.parseInt(v.pop()); 36 | Waves.enemiesleft = 1; 37 | Game.gold = Std.parseInt(v.pop()); 38 | 39 | var numtowers:Int = Std.parseInt(v.pop()); 40 | world.towers.push(Entity.create(GameData.other.goalx, GameData.other.goaly, EntityType.GOAL, world)); 41 | for(i in 0 ... numtowers){ 42 | var towerx:Int = Std.parseInt(v.pop()); 43 | var towery:Int = Std.parseInt(v.pop()); 44 | var towertype:String = v.pop(); 45 | var towertype_enum:EntityType = null; 46 | switch(towertype){ 47 | case "TOWER_SHOOTY": towertype_enum = EntityType.TOWER_SHOOTY; 48 | case "TOWER_BEAM": towertype_enum = EntityType.TOWER_BEAM; 49 | case "TOWER_LASER": towertype_enum = EntityType.TOWER_LASER; 50 | case "TOWER_VORTEX": towertype_enum = EntityType.TOWER_VORTEX; 51 | } 52 | var towerlevel:Int = Std.parseInt(v.pop()); 53 | Game.createtower(towerx, towery, towertype_enum, world, towerlevel); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/Scene.hx: -------------------------------------------------------------------------------- 1 | import scenes.*; 2 | 3 | class Scene { 4 | private static function init() { 5 | currentscene = 0; 6 | callinit(); 7 | } 8 | 9 | private static function callinit() { 10 | if(currentscene == 0){ 11 | Main.init(); 12 | }else if(currentscene == 1){ 13 | TowerDefence.init(); 14 | }else if(currentscene == 2){ 15 | GameOver.init(); 16 | }else if(currentscene == 3){ 17 | TitleScreen.init(); 18 | }else if(currentscene == 4){ 19 | LevelSelect.init(); 20 | } 21 | } 22 | 23 | private static function update() { 24 | if(currentscene == 0){ 25 | Main.update(); 26 | }else if(currentscene == 1){ 27 | TowerDefence.update(); 28 | }else if(currentscene == 2){ 29 | GameOver.update(); 30 | }else if(currentscene == 3){ 31 | TitleScreen.update(); 32 | }else if(currentscene == 4){ 33 | LevelSelect.update(); 34 | } 35 | } 36 | 37 | private static function cleanup() { 38 | if(currentscene == 0){ 39 | Main.cleanup(); 40 | }else if(currentscene == 1){ 41 | TowerDefence.cleanup(); 42 | }else if(currentscene == 2){ 43 | GameOver.cleanup(); 44 | }else if(currentscene == 3){ 45 | TitleScreen.cleanup(); 46 | }else if(currentscene == 4){ 47 | LevelSelect.cleanup(); 48 | } 49 | } 50 | 51 | public static function change(newscene:String) { 52 | //Cleanup scene if possible 53 | hashagon.Core.cleanup(); 54 | cleanup(); 55 | 56 | newscene = hashagon.S.lowercase(newscene); 57 | 58 | if(newscene == "main"){ 59 | currentscene = 0; 60 | }else if(newscene == "towerdefence"){ 61 | currentscene = 1; 62 | }else if(newscene == "gameover"){ 63 | currentscene = 2; 64 | }else if(newscene == "titlescreen"){ 65 | currentscene = 3; 66 | }else if(newscene == "levelselect"){ 67 | currentscene = 4; 68 | }else{ 69 | currentscene = 0; 70 | } 71 | 72 | callinit(); 73 | } 74 | 75 | private static var currentscene:Int; 76 | } 77 | -------------------------------------------------------------------------------- /src/WaveProgress.hx: -------------------------------------------------------------------------------- 1 | import motion.easing.Back; 2 | import hashagon.*; 3 | import hashagon.displayobject.*; 4 | import motion.Actuate; 5 | 6 | class WaveProgress extends h2d.Object{ 7 | public function new(){ 8 | super(); 9 | 10 | y = -30; 11 | panelbacking = new h2d.Graphics(); 12 | panelbacking.beginFill(Col.BLACK); 13 | panelbacking.drawRect(0, 0, Gfx.screenwidth - 21, 20); 14 | panelbacking.endFill(); 15 | panelbacking.beginFill(Col.multiplylightness(Col.GREEN, 0.5)); 16 | panelbacking.drawRect(0, 1, Gfx.screenwidth - 21, 18); 17 | panelbacking.endFill(); 18 | addChild(panelbacking); 19 | 20 | nextwavedisplay = new h2d.Text(Game.textfont, this); 21 | nextwavedisplay.x = (Gfx.screenwidth - 20) / 2; 22 | nextwavedisplay.y = 7; 23 | nextwavedisplay.textAlign = Center; 24 | nextwavedisplay.text = "Wave 1 of 20"; 25 | nextwavedisplay.textColor = Col.WHITE; 26 | } 27 | 28 | public function position(ypos:Float){ 29 | y = ypos; 30 | } 31 | 32 | public function show(wavenum:Int){ 33 | y = -30; 34 | nextwavedisplay.text = "Wave " + wavenum + " of 20"; 35 | Actuate.update(position, 1, [-30], [Gfx.screenheightmid - 10]) 36 | .ease(Back.easeOut) 37 | .onComplete(function(){ 38 | Actuate.update(position, 1, [Gfx.screenheightmid - 10], [Gfx.screenheight + 10]) 39 | 40 | .delay(0.5) 41 | .ease(Back.easeIn) 42 | .onComplete(function(){ 43 | position(-30); 44 | }); 45 | }); 46 | /* 47 | Actuate.tween(this, 0.5, {y: Gfx.screenheightmid - 10}) 48 | .ease(Back.easeIn) 49 | .onComplete(function(){ 50 | Actuate.tween(this, 0.5, {y: Gfx.screenheight + 10}) 51 | .ease(Back.easeOut) 52 | .delay(0.5) 53 | .onComplete(function(){ 54 | y = -30; 55 | }); 56 | });*/ 57 | } 58 | 59 | public function hide(){ 60 | y = -30; 61 | } 62 | 63 | public var nextwavedisplay:h2d.Text; 64 | public var panelbacking:h2d.Graphics; 65 | } -------------------------------------------------------------------------------- /src/scenes/TitleScreen.hx: -------------------------------------------------------------------------------- 1 | package scenes; 2 | 3 | import hashagon.*; 4 | import engine.*; 5 | import hxd.Key; 6 | import h2d.Bitmap; 7 | 8 | class TitleScreen{ 9 | public static function init(){ 10 | titlescreen = new h2d.Anim([Gfx.getimage("background")], 0); 11 | Gfx.core.s2d.addChild(titlescreen); 12 | 13 | heart = new h2d.Anim(Gfx.gettileset("heart").tiles, 1.5); 14 | Gfx.gettileset("heart").tiles[0].dx = -15; 15 | Gfx.gettileset("heart").tiles[0].dy = -15; 16 | Gfx.gettileset("heart").tiles[1].dx = -15; 17 | Gfx.gettileset("heart").tiles[1].dy = -15; 18 | heart.x = Gfx.screenwidthmid; 19 | heart.y = Gfx.screenheightmid; 20 | heart.scale(2.5); 21 | Gfx.core.s2d.addChild(heart); 22 | 23 | logo = new Bitmap(Gfx.getimage("logo"), Gfx.core.s2d); 24 | logo.x = Gfx.screenwidthmid - 77; 25 | logo.y = 45; 26 | 27 | clicktostart = new h2d.Text(Game.smallfont, Gfx.core.s2d); 28 | clicktostart.x = Gfx.screenwidthmid; 29 | clicktostart.y = 98; 30 | clicktostart.textAlign = Center; 31 | clicktostart.text = "GAME BY TERRY CAVANAGH, FOR LD46"; 32 | clicktostart.textColor = 0x888888; 33 | 34 | credits1 = new h2d.Text(Game.smallfont, Gfx.core.s2d); 35 | credits1.x = Gfx.screenwidthmid; 36 | credits1.y = 105; 37 | credits1.textAlign = Center; 38 | credits1.text = "APOLOGIES TO BONNIE TYLER FOR MUSIC"; 39 | credits1.textColor = 0x888888; 40 | } 41 | 42 | public static function update() { 43 | if(Mouse.leftclick()){ 44 | titlescreen.remove(); 45 | heart.remove(); 46 | logo.remove(); 47 | clicktostart.remove(); 48 | 49 | //Music.play("totaleclipse"); 50 | Scene.change("LevelSelect"); 51 | } 52 | } 53 | 54 | public static function cleanup(){ 55 | } 56 | 57 | public static var titlescreen:h2d.Anim; 58 | public static var heart:h2d.Anim; 59 | public static var logo:h2d.Bitmap; 60 | 61 | public static var clicktostart:h2d.Text; 62 | public static var credits1:h2d.Text; 63 | } -------------------------------------------------------------------------------- /src/UIPanel.hx: -------------------------------------------------------------------------------- 1 | import hashagon.*; 2 | 3 | class UIPanel extends h2d.Object{ 4 | public function new(){ 5 | super(); 6 | 7 | x = Gfx.screenwidth - 20; 8 | 9 | //Panel backing 10 | panelbacking = new h2d.Graphics(); 11 | panelbacking.beginFill(Col.BLACK); 12 | panelbacking.drawRect(-1, 0, 1, Gfx.screenheight); 13 | panelbacking.endFill(); 14 | panelbacking.beginFill(Col.multiplylightness(Col.GREEN, 0.5)); 15 | panelbacking.drawRect(0, 0, 20, Gfx.screenheight); 16 | panelbacking.endFill(); 17 | 18 | panelbacking.beginFill(Col.BLACK); 19 | panelbacking.drawRect(-21, Gfx.screenheight - 14, 41, 21); 20 | panelbacking.endFill(); 21 | 22 | panelbacking.beginFill(Col.multiplylightness(Col.GREEN, 0.5)); 23 | panelbacking.drawRect(-20, Gfx.screenheight - 13, 40, 20); 24 | panelbacking.endFill(); 25 | addChild(panelbacking); 26 | 27 | var interaction:h2d.Interactive = new h2d.Interactive(20, Gfx.screenheight, this); 28 | 29 | //Buttons 30 | buttons = []; 31 | addbutton(ButtonType.LASER); 32 | addbutton(ButtonType.BEAM); 33 | addbutton(ButtonType.SHOOTY); 34 | addbutton(ButtonType.VORTEX); 35 | addbutton(ButtonType.UPGRADE); 36 | addbutton(ButtonType.SELL); 37 | 38 | //Cash display 39 | 40 | cashdisplay = new h2d.Text(Game.numberfont, this); 41 | cashdisplay.x = 18; 42 | cashdisplay.y = Gfx.screenheight - 10; 43 | cashdisplay.textAlign = Right; 44 | cashdisplay.text = "$" + Game.gold; 45 | cashdisplay.textColor = Col.YELLOW; 46 | 47 | mouseover = false; 48 | } 49 | 50 | public function updatecashdisplay(){ 51 | cashdisplay.text = "$" + Game.gold; 52 | } 53 | 54 | public function updateallbuttons(){ 55 | for(b in buttons){ 56 | b.checkpressed(); 57 | b.updatebutton(); 58 | } 59 | } 60 | 61 | public function addbutton(type:ButtonType){ 62 | var newbutton:SimpleButton = new SimpleButton(type, buttons.length, this); 63 | buttons.push(newbutton); 64 | } 65 | 66 | public var panelbacking:h2d.Graphics; 67 | 68 | public var buttons:Array; 69 | 70 | public var cashdisplay:h2d.Text; 71 | 72 | public var mouseover:Bool; 73 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "HL debug", 8 | "type": "hxml", 9 | "file": "hl.debug.hxml", 10 | "presentation": { 11 | "reveal": "never", 12 | "panel": "dedicated", 13 | "clear": true 14 | }, 15 | "problemMatcher": [ "$haxe-absolute", "$haxe", "$haxe-error", "$haxe-trace" ], 16 | "group": { 17 | "kind": "build", 18 | "isDefault": true 19 | } 20 | }, 21 | 22 | { 23 | "label": "HL release", 24 | "type": "hxml", 25 | "file": "hl.dx.hxml", 26 | "presentation": { 27 | "reveal": "never", 28 | "panel": "dedicated", 29 | "clear": true 30 | }, 31 | "problemMatcher": [ "$haxe-absolute", "$haxe", "$haxe-error", "$haxe-trace" ], 32 | }, 33 | 34 | { 35 | "label": "JS release", 36 | "type": "hxml", 37 | "file": "js.hxml", 38 | "presentation": { 39 | "reveal": "never", 40 | "panel": "dedicated", 41 | "clear": true 42 | }, 43 | "problemMatcher": [ "$haxe-absolute", "$haxe", "$haxe-error", "$haxe-trace" ], 44 | }, 45 | 46 | { 47 | "label": "Flash release", 48 | "type": "hxml", 49 | "file": "swf.hxml", 50 | "presentation": { 51 | "reveal": "never", 52 | "panel": "dedicated", 53 | "clear": true 54 | }, 55 | "problemMatcher": [ "$haxe-absolute", "$haxe", "$haxe-error", "$haxe-trace" ], 56 | }, 57 | 58 | { 59 | "label": "JS debug", 60 | "type": "hxml", 61 | "file": "js.debug.hxml", 62 | "presentation": { 63 | "reveal": "never", 64 | "panel": "dedicated", 65 | "clear": true 66 | }, 67 | "problemMatcher": ["$haxe"], 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /src/RealSimpleButton.hx: -------------------------------------------------------------------------------- 1 | import h2d.Interactive; 2 | import hashagon.displayobject.*; 3 | import hashagon.*; 4 | import haxe.Constraints.Function; 5 | 6 | class RealSimpleButton extends h2d.Object{ 7 | public function new(text:String, position:Int, onclick:Function, _parent:h2d.Object){ 8 | super(); 9 | _parent.addChild(this); 10 | x = Gfx.screenwidthmid - 40; 11 | y = position; 12 | 13 | buttonbacking = new h2d.Graphics(this); 14 | buttonbacking.moveTo(x, y); 15 | buttonbacking.clear(); 16 | buttonbacking.beginFill(Col.multiplylightness(Col.GREEN, 0.75)); 17 | buttonbacking.drawRect(0, 2, 80, 11); 18 | buttonbacking.endFill(); 19 | buttonbacking.beginFill(Col.GREEN); 20 | buttonbacking.drawRect(0, 0, 80, 11); 21 | buttonbacking.endFill(); 22 | 23 | buttontext = new h2d.Text(Game.smallfont, this); 24 | buttontext.x = 40; 25 | buttontext.y = -2; 26 | buttontext.textAlign = Center; 27 | buttontext.text = text; 28 | buttontext.textColor = Col.WHITE; 29 | 30 | var interaction:h2d.Interactive = new h2d.Interactive(80, 14, this); 31 | 32 | interaction.onOver = function(event : hxd.Event) { 33 | //buttonbacking.alpha = 0.5; 34 | redraw_over(); 35 | } 36 | 37 | interaction.onOut = function(event : hxd.Event) { 38 | //buttonbacking.alpha = 1.0; 39 | redraw_out(); 40 | } 41 | 42 | interaction.onClick = function(event : hxd.Event) { 43 | onclick(); 44 | } 45 | } 46 | 47 | public function redraw_out(){ 48 | buttonbacking.clear(); 49 | buttonbacking.beginFill(Col.multiplylightness(Col.GREEN, 0.75)); 50 | buttonbacking.drawRect(0, 2, 80, 11); 51 | buttonbacking.endFill(); 52 | buttonbacking.beginFill(Col.GREEN); 53 | buttonbacking.drawRect(0, 0, 80, 11); 54 | buttonbacking.endFill(); 55 | 56 | buttontext.y = -2; 57 | } 58 | 59 | public function redraw_over(){ 60 | buttonbacking.clear(); 61 | buttonbacking.beginFill(Col.multiplylightness(Col.GREEN, 0.75)); 62 | buttonbacking.drawRect(0, 2, 80, 11); 63 | buttonbacking.endFill(); 64 | buttonbacking.beginFill(Col.multiplylightness(Col.GREEN, 1.25)); 65 | buttonbacking.drawRect(0, 1, 80, 11); 66 | buttonbacking.endFill(); 67 | 68 | buttontext.y = -1; 69 | } 70 | 71 | public var buttonbacking:h2d.Graphics; 72 | 73 | public var buttontext:h2d.Text; 74 | } -------------------------------------------------------------------------------- /src/hashagon/displayobject/Quad.hx: -------------------------------------------------------------------------------- 1 | package hashagon.displayobject; 2 | 3 | class Quad{ 4 | public function new(){ 5 | graphics = new h2d.Graphics(); 6 | disposed = false; 7 | attached = false; 8 | } 9 | 10 | public function _draw(){ 11 | if(!attached){ 12 | Gfx.core.s2d.addChild(graphics); 13 | attached = true; 14 | }else{ 15 | Gfx.core.s2d.removeChild(graphics); 16 | Gfx.core.s2d.addChild(graphics); 17 | } 18 | } 19 | 20 | public function setpixel(x:Float, y:Float, col:Int, alpha:Float = 1.0) { 21 | if(disposed) return; 22 | if (col == Col.TRANSPARENT) return; 23 | 24 | graphics.beginFill(col, alpha); 25 | graphics.drawRect(x, y, 1, 1); 26 | graphics.endFill(); 27 | 28 | _draw(); 29 | } 30 | 31 | public function drawcircle(x:Float, y:Float, radius:Float, col:Int, alpha:Float = 1.0) { 32 | if(disposed) return; 33 | if (col == Col.TRANSPARENT) return; 34 | 35 | graphics.beginFill(col, alpha); 36 | graphics.drawCircle(x, y, radius); 37 | graphics.endFill(); 38 | 39 | _draw(); 40 | } 41 | 42 | public function fillbox(x:Float, y:Float, width:Float, height:Float, col:Int, alpha:Float = 1.0) { 43 | if(disposed) return; 44 | if (col == Col.TRANSPARENT) return; 45 | 46 | graphics.beginFill(col, alpha); 47 | graphics.drawRect(x, y, width, height); 48 | graphics.endFill(); 49 | 50 | _draw(); 51 | } 52 | 53 | public function drawbox_update(x:Float, y:Float, width:Float, height:Float, col:Int, alpha:Float = 1.0) { 54 | if(disposed) return; 55 | 56 | graphics.moveTo(x, y); 57 | graphics.beginFill(col, alpha); 58 | graphics.drawRect(x, y, width, Gfx.linethickness); 59 | graphics.drawRect(x, y + height - Gfx.linethickness, width, Gfx.linethickness); 60 | graphics.drawRect(x, y + Gfx.linethickness, Gfx.linethickness, height - (Gfx.linethickness * 2)); 61 | graphics.drawRect(x + width - Gfx.linethickness, y + Gfx.linethickness, Gfx.linethickness, height - (Gfx.linethickness * 2)); 62 | graphics.endFill(); 63 | } 64 | 65 | public function drawbox(x:Float, y:Float, width:Float, height:Float, col:Int, alpha:Float = 1.0) { 66 | if(disposed) return; 67 | if (col == Col.TRANSPARENT) return; 68 | 69 | drawbox_update(x, y, width, height, col, alpha); 70 | 71 | _draw(); 72 | } 73 | 74 | public function dispose(){ 75 | disposed = true; 76 | Gfx.core.s2d.removeChild(graphics); 77 | } 78 | 79 | public var graphics:h2d.Graphics; 80 | public var attached:Bool; 81 | public var disposed:Bool; 82 | } -------------------------------------------------------------------------------- /src/hashagon/Rectangle.hx: -------------------------------------------------------------------------------- 1 | package hashagon; 2 | 3 | //Quick reimplementation/copy of Lime/OpenFL's rectangle class that could probably 4 | //be improved a lot 5 | class Rectangle{ 6 | public function new(x:Float = 0, y:Float = 0, width:Float = 0, height:Float = 0){ 7 | this.x = x; 8 | this.y = y; 9 | this.width = width; 10 | this.height = height; 11 | } 12 | 13 | public function setTo(xa:Float, ya:Float, widtha:Float, heighta:Float){ 14 | x = xa; 15 | y = ya; 16 | width = widtha; 17 | height = heighta; 18 | } 19 | 20 | public function intersects(toIntersect:Rectangle):Bool{ 21 | var x0 = x < toIntersect.x ? toIntersect.x : x; 22 | var x1 = right > toIntersect.right ? toIntersect.right : right; 23 | 24 | if (x1 <= x0){ 25 | return false; 26 | } 27 | 28 | var y0 = y < toIntersect.y ? toIntersect.y : y; 29 | var y1 = bottom > toIntersect.bottom ? toIntersect.bottom : bottom; 30 | 31 | return y1 > y0; 32 | } 33 | 34 | public function clone():Rectangle{ 35 | return new Rectangle(x, y, width, height); 36 | } 37 | 38 | /** 39 | Returns whether this rectangle contains the specified (x, y) point 40 | @param x The x coordinate to test 41 | @param y The y coordinate to test 42 | @return Whether the point is contained in the rectangle 43 | **/ 44 | public function contains(x:Float, y:Float):Bool{ 45 | return x >= this.x && y >= this.y && x < right && y < bottom; 46 | } 47 | 48 | public var left(get, set):Float; 49 | @:noCompletion private function get_left():Float{ 50 | return x; 51 | } 52 | 53 | @:noCompletion private function set_left(l:Float):Float{ 54 | width -= l - x; 55 | x = l; 56 | return l; 57 | } 58 | 59 | public var right(get, set):Float; 60 | @:noCompletion private function get_right():Float{ 61 | return x + width; 62 | } 63 | 64 | @:noCompletion private function set_right(r:Float):Float{ 65 | width = r - x; 66 | return r; 67 | } 68 | 69 | public var top(get, set):Float; 70 | @:noCompletion private function get_top():Float{ 71 | return y; 72 | } 73 | 74 | @:noCompletion private function set_top(t:Float):Float{ 75 | height -= t - y; 76 | y = t; 77 | return t; 78 | } 79 | 80 | public var bottom(get, set):Float; 81 | @:noCompletion private function get_bottom():Float{ 82 | return y + height; 83 | } 84 | 85 | @:noCompletion private function set_bottom(b:Float):Float{ 86 | height = b - y; 87 | return b; 88 | } 89 | 90 | public var x:Float; 91 | public var y:Float; 92 | public var width:Float; 93 | public var height:Float; 94 | } -------------------------------------------------------------------------------- /src/Waves.hx: -------------------------------------------------------------------------------- 1 | import engine.*; 2 | 3 | class Wave{ 4 | public function new(type:Int, num:Int, hp:Int, rate:Float, gold:Int, speed:Float, _entrance:Int){ 5 | enemytype = type; 6 | numenemies = num; 7 | spawnrate = rate; 8 | enemyhealth = hp; 9 | reward = gold; 10 | enemyspeed = speed; 11 | entrance = _entrance; 12 | } 13 | 14 | public var enemytype:Int; 15 | public var numenemies:Int; 16 | public var spawnrate:Float; 17 | public var enemyhealth:Int; 18 | public var enemyspeed:Float; 19 | public var reward:Int; 20 | public var entrance:Int; 21 | } 22 | 23 | class Waves{ 24 | public static function init(){ 25 | waves = []; 26 | currentwave = -1; //First wave 27 | enemiesleft = 0; 28 | 29 | var totalwaves = GameData.waves.totalwaves; 30 | 31 | for(i in 0 ... totalwaves){ 32 | var nextwave:Dynamic = Reflect.getProperty(GameData.waves, "wave" + (i + 1)); 33 | waves.push(new Wave( 34 | convertimgtoframe(nextwave.img), 35 | nextwave.num, 36 | nextwave.hp, 37 | nextwave.spawnrate, 38 | nextwave.reward, 39 | nextwave.speed, 40 | nextwave.enter 41 | )); 42 | } 43 | } 44 | 45 | public static function convertimgtoframe(img:String):Int{ 46 | switch(img){ 47 | case "skelly": return 0; 48 | case "knight": return 2; 49 | case "demon": return 4; 50 | case "slime": return 6; 51 | case "wisp": return 8; 52 | case "tree": return 10; 53 | case "rat": return 12; 54 | case "cultist": return 14; 55 | case "skelly2": return 16; 56 | case "knight2": return 18; 57 | case "demon2": return 20; 58 | case "slime2": return 22; 59 | case "wisp2": return 24; 60 | case "tree2": return 26; 61 | case "rat2": return 28; 62 | case "cultist2": return 30; 63 | } 64 | return 0; 65 | } 66 | 67 | public static function nextwave(){ 68 | currentwave++; 69 | enemiesleft = waves[currentwave].numenemies; 70 | currenttype = waves[currentwave].enemytype; 71 | spawnrate = waves[currentwave].spawnrate; 72 | enemyhealth = waves[currentwave].enemyhealth; 73 | enemyspeed = waves[currentwave].enemyspeed; 74 | reward = waves[currentwave].reward; 75 | entrance = waves[currentwave].entrance; 76 | 77 | if(currentwave > 0){ 78 | Game.waveprogress.show(currentwave + 1); 79 | } 80 | Game.playsound("nextwave"); 81 | } 82 | 83 | public static function finalwave():Bool{ 84 | return currentwave >= waves.length - 1; 85 | } 86 | 87 | public static var waves:Array; 88 | 89 | public static var currentwave:Int; 90 | public static var enemiesleft:Int; 91 | public static var enemyhealth:Int; 92 | public static var enemyspeed:Float; 93 | public static var spawnrate:Float; 94 | public static var reward:Int; 95 | public static var currenttype:Int; 96 | public static var entrance:Int; 97 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bat/ 9 | air/ 10 | bin/ 11 | tmp/ 12 | obj/ 13 | AIR_readme.txt 14 | *.lnk 15 | *.tmp 16 | *.bak 17 | *.swp 18 | *.bat 19 | *~.nib 20 | local.properties 21 | .classpath 22 | .settings/ 23 | .loadpath 24 | 25 | # External tool builders 26 | .externalToolBuilders/ 27 | 28 | # Locally stored "Eclipse launch configurations" 29 | *.launch 30 | 31 | # CDT-specific 32 | .cproject 33 | 34 | # PDT-specific 35 | .buildpath 36 | 37 | 38 | ################# 39 | ## Visual Studio 40 | ################# 41 | 42 | ## Ignore Visual Studio temporary files, build results, and 43 | ## files generated by popular Visual Studio add-ons. 44 | 45 | # User-specific files 46 | *.suo 47 | *.user 48 | *.sln.docstates 49 | 50 | # Build results 51 | [Dd]ebug/ 52 | [Rr]elease/ 53 | *_i.c 54 | *_p.c 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.vspscc 69 | .builds 70 | *.dotCover 71 | 72 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 73 | #packages/ 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opensdf 80 | *.sdf 81 | 82 | # Visual Studio profiler 83 | *.psess 84 | *.vsp 85 | 86 | # ReSharper is a .NET coding add-in 87 | _ReSharper* 88 | 89 | # Installshield output folder 90 | [Ee]xpress 91 | 92 | # DocProject is a documentation generator add-in 93 | DocProject/buildhelp/ 94 | DocProject/Help/*.HxT 95 | DocProject/Help/*.HxC 96 | DocProject/Help/*.hhc 97 | DocProject/Help/*.hhk 98 | DocProject/Help/*.hhp 99 | DocProject/Help/Html2 100 | DocProject/Help/html 101 | 102 | # Click-Once directory 103 | publish 104 | 105 | # Others 106 | [Bb]in 107 | [Oo]bj 108 | sql 109 | TestResults 110 | *.Cache 111 | ClientBin 112 | stylecop.* 113 | ~$* 114 | *.dbmdl 115 | Generated_Code #added for RIA/Silverlight projects 116 | 117 | # Backup & report files from converting an old project file to a newer 118 | # Visual Studio version. Backup files are not needed, because we have git ;-) 119 | _UpgradeReport_Files/ 120 | Backup*/ 121 | UpgradeLog*.XML 122 | 123 | 124 | 125 | ############ 126 | ## Windows 127 | ############ 128 | 129 | # Windows image file caches 130 | Thumbs.db 131 | 132 | # Folder config file 133 | Desktop.ini 134 | 135 | 136 | ############# 137 | ## Python 138 | ############# 139 | 140 | *.py[co] 141 | 142 | # Packages 143 | *.egg 144 | *.egg-info 145 | dist 146 | build 147 | eggs 148 | parts 149 | bin 150 | var 151 | sdist 152 | develop-eggs 153 | .installed.cfg 154 | 155 | # Installer logs 156 | pip-log.txt 157 | 158 | # Unit test / coverage reports 159 | .coverage 160 | .tox 161 | 162 | #Translations 163 | *.mo 164 | 165 | #Mr Developer 166 | .mr.developer.cfg 167 | 168 | # Mac crap 169 | .DS_Store 170 | debug.txt -------------------------------------------------------------------------------- /src/cherry/res/ManifestLoader.hx: -------------------------------------------------------------------------------- 1 | package cherry.res; 2 | 3 | import cherry.fs.ManifestFileSystem; 4 | 5 | @:allow(hxd.res.ManifestLoader.LoaderTask) 6 | class ManifestLoader extends hxd.res.Loader 7 | { 8 | 9 | /** 10 | Amount of concurrent file loadings. Defaults to 4 on JS and to 1 native (since there's no threaded loading implemented for native) 11 | **/ 12 | public static var concurrentFiles:Int = #if js 4 #else 1 #end ; 13 | 14 | public var mfs:ManifestFileSystem; 15 | 16 | public var totalFiles(default, null):Int; 17 | public var loadedFiles(default, null):Int; 18 | public var loading(default, null):Bool; 19 | 20 | var entries:Iterator; 21 | var current:ManifestEntry; 22 | /** 23 | List of loading tasks used during loading. 24 | **/ 25 | public var tasks:Array; 26 | 27 | public function new(fs:ManifestFileSystem) 28 | { 29 | super(fs); 30 | mfs = fs; 31 | totalFiles = 0; 32 | for (f in fs.manifest) totalFiles++; 33 | loadedFiles = 0; 34 | loading = false; 35 | } 36 | 37 | public function loadManifestFiles() 38 | { 39 | if (!loading) 40 | { 41 | tasks = new Array(); 42 | for (i in 0...concurrentFiles) tasks.push(@:privateAccess new LoaderTask(i, this)); 43 | loading = true; 44 | entries = mfs.manifest.iterator(); 45 | for (t in tasks) 46 | { 47 | if (entries.hasNext()) t.load(entries.next()); 48 | else break; 49 | } 50 | } 51 | } 52 | 53 | private function next(task:LoaderTask):Void 54 | { 55 | loadedFiles++; 56 | onFileLoaded(task); 57 | if (entries.hasNext()) task.load(entries.next()); 58 | else 59 | { 60 | for (t in tasks) if (t.busy) return; 61 | loading = false; 62 | tasks = null; 63 | onLoaded(); 64 | } 65 | } 66 | 67 | // Called when loader starts loading of specific file. 68 | public dynamic function onFileLoadStarted(task:LoaderTask):Void 69 | { 70 | 71 | } 72 | 73 | // Called during file loading. loaded and total refer to loaded bytes and total file size. 74 | public dynamic function onFileProgress(task:LoaderTask):Void 75 | { 76 | 77 | } 78 | 79 | // Called when file is loaded. 80 | public dynamic function onFileLoaded(task:LoaderTask):Void 81 | { 82 | 83 | } 84 | 85 | public dynamic function onLoaded():Void 86 | { 87 | 88 | } 89 | 90 | } 91 | 92 | class LoaderTask 93 | { 94 | 95 | public var entry(default, null):ManifestEntry; 96 | /** Loading slot occupied by this task **/ 97 | public var slot(default, null):Int; 98 | public var loaded(default, null):Int; 99 | public var total(default, null):Int; 100 | public var owner(default, null):ManifestLoader; 101 | public var busy(default, null):Bool; 102 | 103 | function new(slot:Int, owner:ManifestLoader) 104 | { 105 | this.slot = slot; 106 | this.owner = owner; 107 | } 108 | 109 | public function load(entry:ManifestEntry):Void 110 | { 111 | this.entry = entry; 112 | this.loaded = 0; 113 | this.total = 1; 114 | busy = true; 115 | owner.onFileLoadStarted(this); 116 | entry.fancyLoad(ready, progress); 117 | } 118 | 119 | function ready() 120 | { 121 | busy = false; 122 | @:privateAccess owner.next(this); 123 | } 124 | 125 | function progress(l:Int, t:Int) 126 | { 127 | loaded = l; 128 | total = t; 129 | owner.onFileProgress(this); 130 | } 131 | 132 | } -------------------------------------------------------------------------------- /src/hashagon/Geom.hx: -------------------------------------------------------------------------------- 1 | package hashagon; 2 | 3 | class Geom { 4 | public static inline var PI:Float = 3.141592653589793; 5 | public static inline function abs(v:Float):Float{ return Math.abs(v); } 6 | public static inline function acos(v:Float):Float{ return todegrees(Math.acos(v)); } 7 | public static inline function asin(v:Float):Float{ return todegrees(Math.asin(v)); } 8 | public static inline function atan(v:Float):Float{ return todegrees(Math.atan(v)); } 9 | public static inline function atan2(y:Float, x:Float):Float{ return todegrees(Math.atan2(y, x)); } 10 | public static inline function ceil(v:Float):Float{ return Math.ceil(v); } 11 | public static inline function cos(v:Float):Float{ return Math.cos(toradians(v)); } 12 | public static inline function exp(v:Float):Float{ return Math.exp(v); } 13 | public static inline function floor(v:Float):Float{ return Math.floor(v); } 14 | public static inline function fround(v:Float):Float{ return Math.fround(v); } 15 | public static inline function log(v:Float):Float{ return Math.log(v); } 16 | public static inline function max(a:Float, b:Float):Float{ return Math.max(a, b); } 17 | public static inline function min(a:Float, b:Float):Float{ return Math.min(a, b); } 18 | public static inline function pow(v:Float, exp:Float):Float{ return Math.pow(v, exp); } 19 | public static inline function round(v:Float):Float{ return Math.round(v); } 20 | public static inline function sin(v:Float):Float{ return Math.sin(toradians(v)); } 21 | public static inline function sqrt(v:Float):Float{ return Math.sqrt(v); } 22 | public static inline function tan(v:Float):Float{ return Math.tan(toradians(v)); } 23 | 24 | public static function inbox(x:Float, y:Float, rectx:Float, recty:Float, rectw:Float, recth:Float):Bool { 25 | if (x >= rectx) { 26 | if (x < rectx + rectw) { 27 | if (y >= recty) { 28 | if (y < recty + recth) { 29 | return true; 30 | } 31 | } 32 | } 33 | } 34 | return false; 35 | } 36 | 37 | public static inline function clamp(value:Float, min:Float, max:Float):Float { 38 | return Math.min(max, Math.max(value, min)); 39 | } 40 | 41 | public static function overlap(x1:Float, y1:Float, w1:Float, h1:Float, x2:Float, y2:Float, w2:Float, h2:Float):Bool { 42 | rect1.setTo(x1, y1, w1, h1); 43 | rect2.setTo(x2, y2, w2, h2); 44 | 45 | return rect1.intersects(rect2); 46 | } 47 | 48 | public static inline function distance(x1:Float, y1:Float, x2:Float, y2:Float):Float { 49 | return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); 50 | } 51 | 52 | public static inline function getangle(x1:Float, y1:Float, x2:Float, y2:Float):Float { 53 | return todegrees(((Math.PI * 2) - Math.atan2(y2 - y1, x2 - x1)) % (Math.PI * 2)); 54 | } 55 | 56 | public static inline function todegrees(rad:Float):Float { 57 | return ((rad * 180) / Math.PI); 58 | } 59 | 60 | public static inline function toradians(degrees:Float):Float { 61 | return ((degrees * Math.PI) / 180); 62 | } 63 | 64 | public static inline function anglebetween(angle1:Float, angle2:Float):Float { 65 | return -atan2(sin(angle1 - angle2), cos(angle1 - angle2)); 66 | } 67 | 68 | public static inline function lerp(value:Float, target:Float, f:Float):Float { 69 | return (value + f * (target - value)); 70 | } 71 | 72 | public static inline function range_lerp(value:Float, a1:Float, a2:Float, b1:Float, b2:Float):Float { 73 | return b1 + (value - a1) * (b2 - b1) / (a2 - a1); 74 | } 75 | 76 | public static inline function wrap(v:Float, min:Float, max:Float):Float { 77 | var range = max - min; 78 | return range == 0 ? min : min + ((((v - min) % range) + range) % range); 79 | } 80 | 81 | private static var rect1:Rectangle = new Rectangle(); 82 | private static var rect2:Rectangle = new Rectangle(); 83 | } -------------------------------------------------------------------------------- /src/hashagon/Random.hx: -------------------------------------------------------------------------------- 1 | //Haxegon uses an implementation of George Marsaglia's Xorshift, coded by Andrew Zhilin. 2 | //https://gist.github.com/zoon/865342 3 | //https://en.wikipedia.org/wiki/Xorshift 4 | package hashagon; 5 | 6 | class Random{ 7 | /** Return a random boolean value (true or false) */ 8 | public static function bool():Bool{ 9 | return random() < 0.5; 10 | } 11 | 12 | /** Returns true n% of the time, where n is a float between 0-100, inclusive. */ 13 | public static function chance(n:Float):Bool{ 14 | return float(0, 100) < n; 15 | } 16 | 17 | /** Return a random integer between 'from' and 'to', inclusive. */ 18 | public static function int(from:Int, to:Int):Int { 19 | return randinterval(from, to, true); 20 | } 21 | 22 | /** Return a random float between 'from' and 'to', inclusive. */ 23 | public static function float(from:Float, to:Float):Float{ 24 | return from + ((to - from) * random()); 25 | } 26 | 27 | @:generic 28 | public static function shuffle(arr:Array):Array { 29 | var tmp:T, j:Int, i:Int = arr.length; 30 | while (--i > 0) { 31 | j = Random.int(0, i); 32 | tmp = arr[i]; 33 | arr[i] = arr[j]; 34 | arr[j] = tmp; 35 | } 36 | 37 | return arr; 38 | } 39 | 40 | public static function weighted(options:Array, odds:Array):T{ 41 | //"weighted" is inspired by, and based on chance.js 42 | //http://chancejs.com/#weighted 43 | if (options.length != odds.length){ 44 | trace("Error: in Random.weighted(), both arrays should be the same size."); 45 | trace("Returning a random option from the first array instead."); 46 | return pick(options); 47 | } 48 | 49 | var result:Int = 0; 50 | 51 | var totalodds:Int = 0; 52 | for (i in 0 ... odds.length) totalodds += odds[i]; 53 | 54 | var r:Int = int(0, totalodds); 55 | 56 | var lastid:Int = -1; 57 | totalodds = 0; 58 | for (i in 0 ... options.length) { 59 | var current:Int = odds[i]; 60 | totalodds += current; 61 | if (current > 0) { 62 | if (r <= totalodds) { 63 | result = i; 64 | break; 65 | } 66 | lastid = i; 67 | } 68 | 69 | if (i == (odds.length - 1)) result = lastid; 70 | } 71 | 72 | return options[result]; 73 | } 74 | 75 | /** Return a random string of a certain length. You can optionally specify 76 | which characters to use, otherwise the default is (a-zA-Z0-9) */ 77 | public static function string(length:Int, ?charactersToUse = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"):String{ 78 | var str = ""; 79 | for (i in 0 ... length){ 80 | str += charactersToUse.charAt(int(0, charactersToUse.length - 1)); 81 | } 82 | return str; 83 | } 84 | 85 | public static function pick(arr:Array):T { 86 | return arr[int(0, arr.length - 1)]; 87 | } 88 | 89 | public static var seed(get,set):Dynamic; 90 | private static var _seed:Dynamic = null; 91 | 92 | static function get_seed():Dynamic { 93 | return _seed; 94 | } 95 | 96 | static function set_seed(s:Dynamic) { 97 | if (s == null){ 98 | _seed = Std.int(1 + Math.random() * (INT_MAX - 1)); 99 | }else if (Std.is(s, String)){ 100 | if (s == ""){ 101 | s = Date.now().toString(); 102 | } 103 | 104 | _seed = 0; 105 | for (i in 0 ... s.length){ 106 | _seed += s.charCodeAt(i) * ((i + 1) ^ 2); 107 | } 108 | }else if (Std.is(s, Int)){ 109 | if (s == 0){ 110 | _seed = Std.int(1 + Math.random() * (INT_MAX - 1)); 111 | }else{ 112 | _seed = Std.int(s); 113 | } 114 | }else{ 115 | s = Std.string(s); 116 | _seed = 0; 117 | for (i in 0 ... s.length){ 118 | _seed += s.charCodeAt(i) * ((i + 1) ^ 2); 119 | } 120 | } 121 | 122 | return _seed; 123 | } 124 | 125 | //All the following code is by Andrew Zhilin 126 | //See https://gist.github.com/zoon/865342 127 | 128 | public static function random():Float { 129 | return gen31() / INT_MAXPLUSONE; 130 | } 131 | 132 | /** 133 | * Returns a random Int in [0, INT_MAX] 134 | */ 135 | inline private static function gen31():Int 136 | { 137 | return gen32() & INT_MAX; 138 | } 139 | 140 | /** 141 | * Returns a random Int in [INT_MIN, 0) U (0, INT_MAX] 142 | */ 143 | inline private static function gen32():Int 144 | { 145 | _seed ^= (_seed << 13); 146 | _seed ^= (_seed >>> 17); 147 | return _seed ^= (_seed << 5); 148 | } 149 | 150 | /** 151 | * Return a random Int in [0, n) 152 | */ 153 | private static function rand(n:Int):Int 154 | { 155 | if (n <= 0 || n > INT_MAX) 156 | throw "n out of (0, INT_MAX]"; 157 | var bucket_size = Std.int(INT_MAX / n); 158 | 159 | var r; 160 | do 161 | { 162 | r = Std.int(gen31() / bucket_size); 163 | } 164 | while (r >= n); 165 | return r; 166 | } 167 | 168 | /** 169 | * Returns a random Int in [min, max) by dafault, 170 | * or in [min, max] if includeMax == true; 171 | */ 172 | private static function randinterval(min:Int, max:Int, ?includeMax:Bool=false) 173 | { 174 | if (min == max) return min; 175 | if (max < min) return randinterval(max, min, includeMax); 176 | if (min < 0 || max < 1){ 177 | return rand((max - min) + (includeMax ? 1 : 0)) + min; 178 | } 179 | return min + rand(max - min + (includeMax ? 1 : 0)); 180 | } 181 | 182 | inline private static var INT_MAX:Int = 0x7FFFFFFF; // (2^31 - 1) 183 | inline private static var INT_MAXPLUSONE:Float = 2147483648.0; 184 | } 185 | -------------------------------------------------------------------------------- /src/hashagon/Col.hx: -------------------------------------------------------------------------------- 1 | package hashagon; 2 | 3 | //From Arne's legendary 16 colour palette: http://androidarts.com/palette/16pal.htm 4 | 5 | class Col { 6 | public static var BLACK:Int = 0x000000; 7 | public static var GREY:Int = 0x9D9D9D; 8 | public static var GRAY:Int = 0x9D9D9D; 9 | public static var WHITE:Int = 0xFFFFFF; 10 | public static var RED:Int = 0xBE2633; 11 | public static var PINK:Int = 0xE06F8B; 12 | public static var DARKBROWN:Int = 0x493C2B; 13 | public static var BROWN:Int = 0xA46422; 14 | public static var ORANGE:Int = 0xEB8931; 15 | public static var YELLOW:Int = 0xF7E26B; 16 | public static var DARKGREEN:Int = 0x2F484E; 17 | public static var GREEN:Int = 0x44891A; 18 | public static var LIGHTGREEN:Int = 0xA3CE27; 19 | public static var NIGHTBLUE:Int = 0x1B2632; 20 | public static var DARKBLUE:Int = 0x005784; 21 | public static var BLUE:Int = 0x31A2F2; 22 | public static var LIGHTBLUE:Int = 0xB2DCEF; 23 | public static var MAGENTA:Int = 0xFF00FF; 24 | 25 | public static var TRANSPARENT:Int = 0x000001; 26 | 27 | public static inline function getred(c:Int):Int { 28 | return ((c >> 16) & 0xFF); 29 | } 30 | 31 | public static inline function getgreen(c:Int):Int { 32 | return ((c >> 8) & 0xFF); 33 | } 34 | 35 | public static inline function getblue(c:Int):Int { 36 | return (c & 0xFF); 37 | } 38 | 39 | public static inline function shiftred(c:Int, shift:Float):Int { 40 | return rgb(Std.int(Geom.clamp(getred(c) + shift, 0, 255)), getgreen(c), getblue(c)); 41 | } 42 | 43 | public static inline function shiftgreen(c:Int, shift:Float):Int { 44 | return rgb(getred(c), Std.int(Geom.clamp(getgreen(c) + shift, 0, 255)), getblue(c)); 45 | } 46 | 47 | public static inline function shiftblue(c:Int, shift:Float):Int { 48 | return rgb(getred(c), getgreen(c), Std.int(Geom.clamp(getblue(c) + shift, 0, 255))); 49 | } 50 | 51 | public static function shifthue(c:Int, shift:Float):Int { 52 | if (shift < 0) { 53 | while (shift < 0) shift += 360; 54 | } 55 | return hsl((gethue(c) + Std.int(shift)) % 360, getsaturation(c), getlightness(c)); 56 | } 57 | 58 | public static function multiplysaturation(c:Int, shift:Float):Int { 59 | return hsl(gethue(c), Geom.clamp(getsaturation(c) * shift, 0, 1.0), getlightness(c)); 60 | } 61 | 62 | public static function multiplylightness(c:Int, shift:Float):Int { 63 | return hsl(gethue(c), getsaturation(c), Geom.clamp(getlightness(c) * shift, 0, 1.0)); 64 | } 65 | 66 | /** Get the Hue value (0-360) of a hex code colour. **/ 67 | public static function gethue(c:Int):Int { 68 | var r:Float = getred(c) / 255; 69 | var g:Float = getgreen(c) / 255; 70 | var b:Float = getblue(c) / 255; 71 | var max:Float = Math.max(Math.max(r, g), b); 72 | var min:Float = Math.min(Math.min(r, g), b); 73 | 74 | var h:Float = (max + min) / 2; 75 | 76 | if (max != min) { 77 | var d:Float = max - min; 78 | if(max == r){ 79 | h = (g - b) / d + (g < b ? 6 : 0); 80 | }else if (max == g) { 81 | h = (b - r) / d + 2; 82 | }else if (max == b) { 83 | h = (r - g) / d + 4; 84 | } 85 | h /= 6; 86 | } 87 | 88 | return Std.int(h * 360); 89 | } 90 | 91 | /** Get the Saturation value (0.0-1.0) of a hex code colour. **/ 92 | public static function getsaturation(c:Int):Float { 93 | var r:Float = getred(c) / 255; 94 | var g:Float = getgreen(c) / 255; 95 | var b:Float = getblue(c) / 255; 96 | var max:Float = Math.max(Math.max(r, g), b); 97 | var min:Float = Math.min(Math.min(r, g), b); 98 | 99 | var s:Float = (max + min) / 2; 100 | var l:Float = s; 101 | 102 | if (max == min) { 103 | s = 0; 104 | }else { 105 | var d:Float = max - min; 106 | s = l > 0.5?d / (2 - max - min):d / (max + min); 107 | } 108 | 109 | return s; 110 | } 111 | 112 | /** Get the Lightness value (0.0-1.0) of a hex code colour. **/ 113 | public static function getlightness(c:Int):Float { 114 | var r:Float = getred(c) / 255; 115 | var g:Float = getgreen(c) / 255; 116 | var b:Float = getblue(c) / 255; 117 | var max:Float = Math.max(Math.max(r, g), b); 118 | var min:Float = Math.min(Math.min(r, g), b); 119 | 120 | return (max + min) / 2; 121 | } 122 | 123 | public static function rgb(red:Int, green:Int, blue:Int):Int { 124 | return (blue | (green << 8) | (red << 16)); 125 | } 126 | 127 | /** Picks a colour given Hue, Saturation and Lightness values. 128 | * Hue is between 0-359, Saturation and Lightness between 0.0 and 1.0. */ 129 | public static function hsl(hue:Float, saturation:Float, lightness:Float):Int { 130 | var q:Float = if (lightness < 1 / 2) { 131 | lightness * (1 + saturation); 132 | }else { 133 | lightness + saturation - (lightness * saturation); 134 | } 135 | 136 | var p:Float = 2 * lightness - q; 137 | 138 | var hk:Float = ((hue % 360) / 360); 139 | 140 | hslval[0] = hk + 1 / 3; 141 | hslval[1] = hk; 142 | hslval[2] = hk - 1 / 3; 143 | for (n in 0 ... 3){ 144 | if (hslval[n] < 0) hslval[n] += 1; 145 | if (hslval[n] > 1) hslval[n] -= 1; 146 | hslval[n] = if (hslval[n] < 1 / 6){ 147 | p + ((q - p) * 6 * hslval[n]); 148 | }else if (hslval[n] < 1 / 2) { 149 | q; 150 | }else if (hslval[n] < 2 / 3){ 151 | p + ((q - p) * 6 * (2 / 3 - hslval[n])); 152 | }else{ 153 | p; 154 | } 155 | } 156 | 157 | return rgb(Std.int(hslval[0] * 255), Std.int(hslval[1] * 255), Std.int(hslval[2] * 255)); 158 | } 159 | //HSL conversion variables 160 | private static var hslval:Array = [0.0, 0.0, 0.0]; 161 | } -------------------------------------------------------------------------------- /src/cherry/fs/ManifestBuilder.hx: -------------------------------------------------------------------------------- 1 | package cherry.fs; 2 | 3 | import hxd.res.EmbedOptions; 4 | import haxe.io.Path; 5 | import haxe.io.Bytes; 6 | 7 | typedef ManifestFileInfo = { 8 | var path:String; 9 | var original:String; 10 | } 11 | 12 | class ManifestBuilder 13 | { 14 | public static macro function initManifest( ?basePath : String, ?options : hxd.res.EmbedOptions, ?storeManifest:String ) 15 | { 16 | var data = makeManifest(basePath, options, storeManifest); 17 | if (basePath == null) basePath = "res"; 18 | return macro { 19 | var loader = new cherry.res.ManifestLoader(@:privateAccess new cherry.fs.ManifestFileSystem($v{basePath}, haxe.io.Bytes.ofString($v{data.manifest.toString()}))); 20 | hxd.Res.loader = loader; 21 | loader; 22 | } 23 | } 24 | 25 | public static macro function generate( ?basePath : String, ?options : hxd.res.EmbedOptions, ?storeManifest:String ) 26 | { 27 | var data = makeManifest(basePath, options, storeManifest); 28 | return macro {}; 29 | } 30 | 31 | public static macro function create( ?basePath : String, ?options : hxd.res.EmbedOptions, ?storeManifest:String ) 32 | { 33 | var data = makeManifest(basePath, options, storeManifest); 34 | 35 | // var types = { 36 | // expr : haxe.macro.Expr.ExprDef.EBlock([for( t in data.types ) haxe.macro.MacroStringTools.toFieldExpr(t.split("."))]), 37 | // pos : haxe.macro.Context.currentPos(), 38 | // }; 39 | 40 | return macro @:privateAccess new engine.utils.fs.ManifestFileSystem($v{basePath}, haxe.io.Bytes.ofString($v{data.manifest.toString()})); 41 | } 42 | 43 | #if macro 44 | 45 | private static function makeManifest(?basePath : String, ?options : hxd.res.EmbedOptions, ?storeManifest:String) 46 | { 47 | var f = new hxd.res.FileTree(basePath); 48 | var manifest:Bytes = build(f, options); 49 | 50 | if (storeManifest != null) 51 | { 52 | if (!haxe.macro.Context.defined("display")) 53 | { 54 | #if !display 55 | var tmp:String = Path.join([@:privateAccess f.paths[0], storeManifest + ".manifest"]); 56 | sys.io.File.saveBytes(tmp, manifest); 57 | #end 58 | } 59 | } 60 | return { tree: f, manifest: manifest }; 61 | } 62 | 63 | // private static function makeTree(t:hxd.res.FileTree.FileTreeData, out:Array):Void 64 | // { 65 | // for (f in t.files) out.push(f.relPath); 66 | // for (d in t.dirs) makeTree(d, out); 67 | // } 68 | 69 | public static function build(tree:hxd.res.FileTree, ?options: hxd.res.EmbedOptions, ?manifestOptions:ManifestOptions):Bytes 70 | { 71 | var manifest:Array = new Array(); 72 | var data = scan(tree, options, manifest); 73 | // makeTree(data, manifest); 74 | 75 | if (manifestOptions == null) 76 | { 77 | manifestOptions = { format: ManifestFormat.Json }; 78 | } 79 | if (manifestOptions.format == null) manifestOptions.format = ManifestFormat.List; 80 | 81 | switch (manifestOptions.format) 82 | { 83 | // case ManifestFormat.List: 84 | // return haxe.io.Bytes.ofString("l\n" + manifest.join("\n")); 85 | case Json: 86 | return haxe.io.Bytes.ofString(haxe.Json.stringify(manifest)); 87 | case v: 88 | // case ManifestFormat.KeyValue: 89 | 90 | // case ManifestFormat.Serialized: 91 | 92 | // case ManifestFormat.Json: 93 | throw "unsupported manifest foramt: " + v; 94 | } 95 | } 96 | 97 | static var options:EmbedOptions; 98 | static function scan(t:hxd.res.FileTree, options:EmbedOptions, out:Array) 99 | { 100 | if( options == null ) options = { }; 101 | // var setTmp = options.tmpDir == null; 102 | // if( options.compressAsMp3 == null ) options.compressAsMp3 = options.compressSounds && !(haxe.macro.Context.defined("stb_ogg_sound") || hxd.res.Config.platform == HL); 103 | ManifestBuilder.options = options; 104 | 105 | var tree = @:privateAccess t.scan(); 106 | 107 | for( path in @:privateAccess t.paths ) 108 | { 109 | // if( setTmp ) options.tmpDir = path + "/.tmp/"; 110 | 111 | var fs = new hxd.fs.LocalFileSystem(path, options.configuration); 112 | // if( options.compressAsMp3 ) 113 | // fs.addConvert(new hxd.fs.Convert.ConvertWAV2MP3()); 114 | // else if( options.compressSounds ) 115 | // fs.addConvert(new hxd.fs.Convert.ConvertWAV2MP3()); 116 | // fs.tmpDir = options.tmpDir; 117 | fs.convert.onConvert = function(f) Sys.println("Converting " + f.srcPath); 118 | convertRec(tree, path, fs, out); 119 | } 120 | return tree; 121 | } 122 | 123 | static function convertRec( tree : hxd.res.FileTree.FileTreeData, basePath : String, fs : hxd.fs.LocalFileSystem, out:Array ) 124 | { 125 | @:privateAccess for( file in tree.files ) { 126 | // try later with another fs 127 | if( !StringTools.startsWith(file.fullPath, basePath) ) 128 | continue; 129 | var info = { path: file.relPath, original: file.relPath }; 130 | out.push(info); 131 | var f = fs.get(file.relPath); // convert 132 | if (f.originalFile != null && f.originalFile != f.file) 133 | { 134 | info.original = f.relPath; 135 | info.path = StringTools.startsWith(f.file, fs.baseDir) ? f.file.substr(fs.baseDir.length) : f.file; 136 | } 137 | } 138 | 139 | for( t in tree.dirs ) 140 | convertRec(t, basePath, fs, out); 141 | } 142 | 143 | #end 144 | 145 | } 146 | 147 | typedef ManifestOptions = 148 | { 149 | @:optional var format:ManifestFormat; 150 | } 151 | 152 | enum ManifestFormat 153 | { 154 | KeyValue; 155 | List; 156 | Serialized; 157 | Json; 158 | } -------------------------------------------------------------------------------- /src/engine/World.hx: -------------------------------------------------------------------------------- 1 | package engine; 2 | 3 | import h2d.TileGroup; 4 | import hashagon.displayobject.*; 5 | import hashagon.*; 6 | 7 | class World{ 8 | public function new(){ 9 | init(); 10 | } 11 | 12 | public function init(){ 13 | tileset = ""; 14 | tilewidth = 0; 15 | tileheight = 0; 16 | outsidetile = 0; 17 | 18 | contents = null; 19 | width = 0; 20 | height = 0; 21 | tilegroup = null; 22 | } 23 | 24 | public function loadtiles(_tilesetname:String, _w:Int, _h:Int){ 25 | tileset = _tilesetname; 26 | tilewidth = _w; 27 | tileheight = _h; 28 | Gfx.loadtiles(_tilesetname, _w, _h); 29 | 30 | collidable = []; 31 | canplacetower = []; 32 | for (i in 0 ... Gfx.numberoftiles(tileset)){ 33 | collidable[i] = false; 34 | canplacetower[i] = false; 35 | } 36 | } 37 | 38 | public function setcollidable(collidabletiles:Array){ 39 | for (i in 0 ... Gfx.numberoftiles(tileset)){ 40 | if(collidabletiles.indexOf(i) > -1){ 41 | collidable[i] = true; 42 | } 43 | } 44 | } 45 | 46 | public function setcanplacetower(canplacetowertiles:Array){ 47 | for (i in 0 ... Gfx.numberoftiles(tileset)){ 48 | if(canplacetowertiles.indexOf(i) > -1){ 49 | canplacetower[i] = true; 50 | } 51 | } 52 | } 53 | 54 | public function changesize(_w:Int, _h:Int){ 55 | width = _w; 56 | height = _h; 57 | contents = Core.create2darray(width, height, 0); 58 | } 59 | 60 | public function randomcontents(_w:Int, _h:Int){ 61 | changesize(_w, _h); 62 | 63 | var numtiles:Int = Gfx.numberoftiles(tileset); 64 | for (j in 0 ... _h){ 65 | for (i in 0 ... _w){ 66 | contents[i][j] = Random.int(0, numtiles - 1); 67 | } 68 | } 69 | } 70 | 71 | public function loadcsv(csvfile:String, delimiter:String = ","){ 72 | //csvfile = normalizefilename(csvfile, "data/text/", "csv"); 73 | var tempstring:String = ""; 74 | if(csvfile == "stage1") tempstring = hxd.Res.text.stage1.entry.getText(); 75 | if(csvfile == "stage2") tempstring = hxd.Res.text.stage2.entry.getText(); 76 | 77 | //figure out width 78 | var csvwidth:Int = 1; 79 | var i:Int = 0; 80 | while (i < tempstring.length) { 81 | if (S.mid(tempstring, i) == delimiter) csvwidth++; 82 | if (S.mid(tempstring, i) == "\n") { 83 | break; 84 | } 85 | i++; 86 | } 87 | 88 | tempstring = S.replacechar(tempstring, "\r", ""); 89 | tempstring = S.replacechar(tempstring, "\n", ""); 90 | var returnedarray:Array = new Array(); 91 | var stringarray:Array = tempstring.split(delimiter); 92 | 93 | for (i in 0 ... stringarray.length) { 94 | returnedarray.push(Std.parseInt(stringarray[i])); 95 | } 96 | 97 | var csvheight:Int = Std.int(returnedarray.length / csvwidth); 98 | 99 | csvwidth -= 1; 100 | csvheight += 1; 101 | changesize(csvwidth, csvheight); 102 | for(y in 0 ... csvheight){ 103 | for (x in 0 ... csvwidth) { 104 | var tid:Int = returnedarray[x + (y * csvwidth)]; 105 | contents[x][y] = returnedarray[x + (y * csvwidth)] - 1; 106 | } 107 | } 108 | } 109 | 110 | public function refreshmap(){ 111 | var maptileset:Tileset = Gfx.gettileset(tileset); 112 | tilegroup = new TileGroup(maptileset.tilesetdata); 113 | for(j in 0 ... height){ 114 | for(i in 0 ... width){ 115 | tilegroup.add(Std.int(i * tilewidth), Std.int(j * tileheight), maptileset.tiles[contents[i][j]]); 116 | } 117 | } 118 | Game.backgroundlayer.addChild(tilegroup); 119 | } 120 | 121 | public function checkpathsquare(x:Int, y:Int, t:Int){ 122 | if(Geom.inbox(x, y, 0, 0, width, height)){ 123 | if(!collidable[contents[x][y]]){ 124 | if(t < heatmap[x][y]){ 125 | heatmap[x][y] = t; 126 | t++; 127 | checkpathsquare(x - 1, y, t); 128 | checkpathsquare(x + 1, y, t); 129 | checkpathsquare(x, y - 1, t); 130 | checkpathsquare(x, y + 1, t); 131 | } 132 | } 133 | } 134 | } 135 | 136 | /* Get the heat at position x, y */ 137 | public function heatat(x:Float, y:Float, fixtogrid:Bool = true):Int{ 138 | if(fixtogrid){ 139 | x = gridx(x); 140 | y = gridy(y); 141 | } 142 | if(Geom.inbox(x, y, 0, 0, width, height)){ 143 | return heatmap[Std.int(x)][Std.int(y)]; 144 | }else{ 145 | return 10000; 146 | } 147 | } 148 | 149 | //Figure out a heatmap coming from a given point 150 | public function getheatmap(fromx:Int, fromy:Int){ 151 | //Clear the heatmap 152 | heatmap = Core.create2darray(width, height, 10000); 153 | checkpathsquare(fromx, fromy, 1); 154 | } 155 | 156 | //Figure out a heatmap coming from a given point 157 | public function getdoubleheatmap(fromx:Int, fromy:Int, fromx2:Int, fromy2:Int){ 158 | //Clear the heatmap 159 | heatmap = Core.create2darray(width, height, 10000); 160 | checkpathsquare(fromx, fromy, 1); 161 | checkpathsquare(fromx2, fromy2, 1); 162 | } 163 | 164 | public function render(){ 165 | } 166 | 167 | public function gridx(pixelx:Float):Int{ 168 | return Std.int((pixelx - (Std.int(pixelx) % tilewidth)) / tilewidth); 169 | } 170 | 171 | public function gridy(pixely:Float):Int{ 172 | return Std.int((pixely - (Std.int(pixely) % tileheight)) / tileheight); 173 | } 174 | 175 | public function gridxoffset(pixelx:Float):Float{ 176 | return pixelx - (gridx(pixelx) * tilewidth); 177 | } 178 | 179 | public function gridyoffset(pixely:Float):Float{ 180 | return pixely - (gridy(pixely) * tileheight); 181 | } 182 | 183 | public function destroy(){ 184 | Game.backgroundlayer.removeChild(tilegroup); 185 | } 186 | 187 | public var contents:Array>; 188 | public var heatmap:Array>; 189 | public var tilegroup:h2d.TileGroup; 190 | public var width:Int; 191 | public var height:Int; 192 | 193 | public var collidable:Array; 194 | public var canplacetower:Array; 195 | 196 | public var tileset:String; 197 | public var tilewidth:Int; 198 | public var tileheight:Int; 199 | 200 | public var outsidetile:Int; 201 | 202 | public var monsters:Array; 203 | public var towers:Array; 204 | public var bullets:Array; 205 | public var particles:Array; 206 | } -------------------------------------------------------------------------------- /src/SimpleButton.hx: -------------------------------------------------------------------------------- 1 | import h2d.Interactive; 2 | import hashagon.displayobject.*; 3 | import hashagon.*; 4 | 5 | class SimpleButton extends h2d.Object{ 6 | public function new(_type:ButtonType, position:Int, _parent:h2d.Object){ 7 | super(); 8 | _parent.addChild(this); 9 | 10 | type = _type; 11 | x = 3; 12 | y = 2 + (position * 17); 13 | 14 | pressed = false; 15 | buttoncol = Col.GREEN; 16 | 17 | buttonbacking = new h2d.Graphics(this); 18 | 19 | var tileset:Tileset = Gfx.gettileset("towers"); 20 | icon = new h2d.Anim(tileset.tiles, 0, this); 21 | switch (type){ 22 | case ButtonType.LASER: 23 | icon.currentFrame = 12; 24 | case ButtonType.BEAM: 25 | icon.currentFrame = 18; 26 | case ButtonType.VORTEX: 27 | icon.currentFrame = 6; 28 | case ButtonType.SHOOTY: 29 | icon.currentFrame = 0; 30 | case ButtonType.UPGRADE: 31 | icon.currentFrame = 42; 32 | case ButtonType.SELL: 33 | icon.currentFrame = 43; 34 | } 35 | 36 | inittooltips(_type); 37 | 38 | checkpressed(); 39 | updatebutton(); 40 | 41 | var interaction:h2d.Interactive = new h2d.Interactive(14, 14, this); 42 | 43 | interaction.onOver = function(event : hxd.Event) { 44 | icon.alpha = 0.7; 45 | buttonbacking.alpha = 0.7; 46 | showtooltips(); 47 | } 48 | 49 | interaction.onOut = function(event : hxd.Event) { 50 | icon.alpha = 1.0; 51 | buttonbacking.alpha = 1.0; 52 | hidetooltips(); 53 | } 54 | 55 | interaction.onClick = function(event : hxd.Event) { 56 | Game.changeselectedmode(type); 57 | } 58 | } 59 | 60 | public function checkpressed(){ 61 | pressed = false; 62 | if(Game.cursormode == type) pressed = true; 63 | } 64 | 65 | public function updatebutton(){ 66 | canafford = true; 67 | buttoncol = Col.GREEN; 68 | 69 | switch (type){ 70 | case ButtonType.LASER: 71 | if(Game.gold < GameData.towers.laser.cost) canafford = false; 72 | case ButtonType.BEAM: 73 | if(Game.gold < GameData.towers.beam.cost) canafford = false; 74 | case ButtonType.VORTEX: 75 | if(Game.gold < GameData.towers.vortex.cost) canafford = false; 76 | case ButtonType.SHOOTY: 77 | if(Game.gold < GameData.towers.shooty.cost) canafford = false; 78 | default: 79 | } 80 | 81 | if(!canafford){ 82 | buttoncol = Col.GRAY; 83 | } 84 | 85 | if(pressed){ 86 | buttonbacking.clear(); 87 | buttonbacking.beginFill(Col.multiplylightness(buttoncol, 0.75)); 88 | buttonbacking.drawRect(0, 12, 14, 3); 89 | buttonbacking.endFill(); 90 | buttonbacking.beginFill(Col.multiplylightness(buttoncol, 1.2)); 91 | buttonbacking.drawRect(0, 2, 14, 12); 92 | buttonbacking.endFill(); 93 | 94 | icon.x = 2; icon.y = 3; 95 | }else{ 96 | buttonbacking.clear(); 97 | buttonbacking.beginFill(Col.multiplylightness(buttoncol, 0.75)); 98 | buttonbacking.drawRect(0, 12, 14, 3); 99 | buttonbacking.endFill(); 100 | buttonbacking.beginFill(buttoncol); 101 | buttonbacking.drawRect(0, 0, 14, 12); 102 | buttonbacking.endFill(); 103 | 104 | icon.x = 2; icon.y = 1; 105 | } 106 | 107 | buttoncol = Col.GREEN; 108 | } 109 | 110 | public function inittooltips(_type:ButtonType){ 111 | tooltipbacking = new h2d.Graphics(this); 112 | tooltipbacking.y = -1; 113 | 114 | tooltipname = new h2d.Text(Game.textfont, this); 115 | tooltipname.x = -4; 116 | tooltipname.y = 0; 117 | tooltipname.textAlign = Right; 118 | tooltipname.textColor = Col.WHITE; 119 | 120 | tooltipprice = new h2d.Text(Game.numberfont, this); 121 | tooltipprice.x = -4; 122 | tooltipprice.y = 9; 123 | tooltipprice.textAlign = Right; 124 | tooltipprice.textColor = Col.YELLOW; 125 | 126 | switch(_type){ 127 | case ButtonType.LASER: 128 | tooltipname.text = "Laser"; 129 | tooltipprice.text = "$" + GameData.towers.laser.cost; 130 | tooltipbacking.beginFill(Col.multiplylightness(buttoncol, 0.8)); 131 | tooltipbacking.drawRect(-34 - 20, 0, 50, 18); 132 | tooltipbacking.endFill(); 133 | case ButtonType.BEAM: 134 | tooltipname.text = "Beam"; 135 | tooltipprice.text = "$" + GameData.towers.beam.cost; 136 | tooltipbacking.beginFill(Col.multiplylightness(buttoncol, 0.8)); 137 | tooltipbacking.drawRect(-34- 10, 0, 40, 18); 138 | tooltipbacking.endFill(); 139 | case ButtonType.VORTEX: 140 | tooltipname.text = "Vortex"; 141 | tooltipprice.text = "$" + GameData.towers.vortex.cost; 142 | tooltipbacking.beginFill(Col.multiplylightness(buttoncol, 0.8)); 143 | tooltipbacking.drawRect(-34 - 30, 0, 60, 18); 144 | tooltipbacking.endFill(); 145 | case ButtonType.SHOOTY: 146 | tooltipname.text = "Mines"; 147 | tooltipprice.text = "$" + GameData.towers.shooty.cost; 148 | tooltipbacking.beginFill(Col.multiplylightness(buttoncol, 0.8)); 149 | tooltipbacking.drawRect(-34- 20, 0, 50, 18); 150 | tooltipbacking.endFill(); 151 | case ButtonType.UPGRADE: 152 | tooltipname.text = "Upgrade"; 153 | tooltipname.y += 5; 154 | tooltipprice.text = ""; 155 | tooltipbacking.beginFill(Col.multiplylightness(buttoncol, 0.8)); 156 | tooltipbacking.drawRect(-42- 30, 0, 68, 18); 157 | tooltipbacking.endFill(); 158 | case ButtonType.SELL: 159 | tooltipname.text = "Sell"; 160 | tooltipname.y += 5; 161 | tooltipprice.text = ""; 162 | tooltipbacking.beginFill(Col.multiplylightness(buttoncol, 0.8)); 163 | tooltipbacking.drawRect(-34- 10, 0, 40, 18); 164 | tooltipbacking.endFill(); 165 | } 166 | 167 | tooltipbacking.visible = false; 168 | tooltipname.visible = false; 169 | tooltipprice.visible = false; 170 | } 171 | 172 | public function showtooltips(){ 173 | tooltipbacking.visible = true; 174 | tooltipname.visible = true; 175 | tooltipprice.visible = true; 176 | } 177 | 178 | public function hidetooltips(){ 179 | tooltipbacking.visible = false; 180 | tooltipname.visible = false; 181 | tooltipprice.visible = false; 182 | } 183 | 184 | public var buttonbacking:h2d.Graphics; 185 | public var icon:h2d.Anim; 186 | public var type:ButtonType; 187 | 188 | public var buttoncol:Int; 189 | public var canafford:Bool; 190 | 191 | public var tooltipbacking:h2d.Graphics; 192 | public var tooltipname:h2d.Text; 193 | public var tooltipprice:h2d.Text; 194 | 195 | public var pressed:Bool; 196 | } -------------------------------------------------------------------------------- /src/hashagon/Gfx.hx: -------------------------------------------------------------------------------- 1 | package hashagon; 2 | 3 | import hashagon.displayobject.*; 4 | 5 | class Gfx{ 6 | public static function init(){ 7 | graphicsmap = new Map(); 8 | imagemap = new Map(); 9 | imageindex = new Map(); 10 | 11 | tilemap = new Map(); 12 | tileindex = new Map(); 13 | 14 | clearbuffer = new Quad(); 15 | linethickness = 1; 16 | } 17 | 18 | /** Loads an image into the game. */ 19 | public static function loadimage(imagename:String):Bool { 20 | imagename = imagename.toLowerCase(); 21 | if (imageindex.exists(imagename)) return true; //This is already loaded, so we're done! 22 | 23 | //We're just gonna assume this exists already 24 | var newtile:h2d.Tile = hxd.Res.load("graphics/" + imagename + ".png").toTile(); 25 | imageindex.set(imagename, newtile); 26 | 27 | return true; 28 | } 29 | 30 | public static function loadtiles(imagename:String, width:Int, height:Int):Bool { 31 | imagename = imagename.toLowerCase(); 32 | if (tileindex.exists(imagename)) return true; //This is already loaded, so we're done! 33 | 34 | //We're just gonna assume this exists already 35 | var newtileset:Tileset = new Tileset(imagename, width, height); 36 | tileindex.set(imagename, newtileset); 37 | 38 | return true; 39 | } 40 | 41 | public static function numberoftiles(tileset:String):Int { 42 | return gettileset(tileset).numtiles; 43 | } 44 | 45 | public static function gettileset(tileset:String):Tileset{ 46 | return tileindex.get(tileset); 47 | } 48 | 49 | public static function getimage(imagename:String):h2d.Tile{ 50 | return imageindex.get(imagename); 51 | } 52 | 53 | public static function drawtile(x:Float, y:Float, tilesetname:String, tilenum:Int, id:String = "", ?pos:haxe.PosInfos){ 54 | if(id == "") id = pos.fileName + "_" + pos.lineNumber; 55 | 56 | var t:Tile; 57 | if (tilemap.exists(id)){ 58 | t = tilemap.get(id); 59 | }else{ 60 | t = new Tile(); 61 | tilemap.set(id, t); 62 | } 63 | 64 | t.drawtile(x, y, tilesetname, tilenum); 65 | } 66 | 67 | public static function drawimage(x:Float, y:Float, imagename:String, id:String = "", ?pos:haxe.PosInfos){ 68 | if(id == "") id = pos.fileName + "_" + pos.lineNumber; 69 | 70 | var img:Image; 71 | if (imagemap.exists(id)){ 72 | img = imagemap.get(id); 73 | core.s2d.removeChild(img.bitmap); 74 | }else{ 75 | img = new Image(); 76 | imagemap.set(id, img); 77 | } 78 | 79 | img.drawimage(x, y, imagename); 80 | } 81 | 82 | public static function fillbox(x:Float, y:Float, width:Float, height:Float, col:Int, alpha:Float = 1.0, id:String = "", ?pos:haxe.PosInfos) { 83 | if(id == "") id = pos.fileName + "_" + pos.lineNumber; 84 | 85 | var box:Quad; 86 | if (graphicsmap.exists(id)){ 87 | box = graphicsmap.get(id); 88 | core.s2d.removeChild(box.graphics); 89 | }else{ 90 | box = new Quad(); 91 | graphicsmap.set(id, box); 92 | } 93 | 94 | box.fillbox(x, y, width, height, col, alpha); 95 | } 96 | 97 | public static function drawbox(x:Float, y:Float, width:Float, height:Float, col:Int, alpha:Float = 1.0, id:String = "", ?pos:haxe.PosInfos) { 98 | if(id == "") id = pos.fileName + "_" + pos.lineNumber; 99 | 100 | var box:Quad; 101 | if (graphicsmap.exists(id)){ 102 | box = graphicsmap.get(id); 103 | box.drawbox_update(x, y, width, height, col, alpha); 104 | }else{ 105 | box = new Quad(); 106 | graphicsmap.set(id, box); 107 | box.drawbox(x, y, width, height, col, alpha); 108 | } 109 | } 110 | 111 | public static function drawcircle(x:Float, y:Float, radius:Float, col:Int, alpha:Float = 1.0, id:String = "", ?pos:haxe.PosInfos) { 112 | if(id == "") id = pos.fileName + "_" + pos.lineNumber; 113 | 114 | var circle:Quad; 115 | if (graphicsmap.exists(id)){ 116 | circle = graphicsmap.get(id); 117 | circle.drawcircle(x, y, radius, col, alpha); 118 | }else{ 119 | circle = new Quad(); 120 | graphicsmap.set(id, circle); 121 | circle.drawcircle(x, y, radius, col, alpha); 122 | } 123 | } 124 | 125 | public static function setpixel(x:Float, y:Float, col:Int, alpha:Float = 1.0, id:String = "", ?pos:haxe.PosInfos) { 126 | if(id == "") id = pos.fileName + "_" + pos.lineNumber; 127 | 128 | var box:Quad; 129 | if (graphicsmap.exists(id)){ 130 | box = graphicsmap.get(id); 131 | core.s2d.removeChild(box.graphics); 132 | }else{ 133 | box = new Quad(); 134 | graphicsmap.set(id, box); 135 | } 136 | 137 | box.setpixel(x, y, col, alpha); 138 | } 139 | 140 | public static function clearscreen(color:Int = 0x000000) { 141 | if (color != Col.TRANSPARENT){ 142 | clearbuffer.fillbox(0, 0, screenwidth, screenheight, color); 143 | } 144 | } 145 | 146 | public static function cleanup(){ 147 | if(graphicsmap != null){ 148 | for (k in graphicsmap.keys()){ 149 | graphicsmap.get(k).dispose(); 150 | graphicsmap.remove(k); 151 | } 152 | } 153 | 154 | if(tilemap != null){ 155 | for (k in tilemap.keys()){ 156 | tilemap.get(k).dispose(); 157 | tilemap.remove(k); 158 | } 159 | } 160 | 161 | if(imagemap != null){ 162 | for (k in imagemap.keys()){ 163 | imagemap.get(k).dispose(); 164 | imagemap.remove(k); 165 | } 166 | } 167 | } 168 | 169 | public static var linethickness(get,set):Float; 170 | private static var _linethickness:Float; 171 | 172 | static function get_linethickness():Float { 173 | return _linethickness; 174 | } 175 | 176 | static function set_linethickness(size:Float) { 177 | _linethickness = size; 178 | if (_linethickness < 1) _linethickness = 1; 179 | return _linethickness; 180 | } 181 | 182 | public static var imageindex:Map; 183 | public static var tileindex:Map; 184 | 185 | public static var screenwidth(get, null):Int; 186 | @:noCompletion private static function get_screenwidth():Int{ return core.s2d.width; } 187 | public static var screenheight(get, null):Int; 188 | @:noCompletion private static function get_screenheight():Int{ return core.s2d.height; } 189 | public static var screenwidthmid(get, null):Int; 190 | @:noCompletion private static function get_screenwidthmid():Int{ return Std.int(core.s2d.width / 2); } 191 | public static var screenheightmid(get, null):Int; 192 | @:noCompletion private static function get_screenheightmid():Int{ return Std.int(core.s2d.height / 2); } 193 | 194 | public static var core:Core; 195 | private static var graphicsmap:Map; 196 | private static var imagemap:Map; 197 | private static var tilemap:Map; 198 | private static var clearbuffer:Quad; 199 | 200 | public static var clearcolor:Int = 0x000000; 201 | } -------------------------------------------------------------------------------- /data/text/variables.json: -------------------------------------------------------------------------------- 1 | { 2 | "towers": { 3 | "laser": { 4 | "cost": 5, 5 | "level1": { "firerate": 0.25, "radius": 25, "damage": 1, "upgradecost": 3 }, 6 | "level2": { "firerate": 0.25, "radius": 30, "damage": 1.5, "upgradecost": 6 }, 7 | "level3": { "firerate": 0.25, "radius": 35, "damage": 2 } 8 | }, 9 | "beam": { 10 | "cost": 15, 11 | "level1": { "firerate": 2.0, "radius": 1000, "damage": 10, "upgradecost": 10 }, 12 | "level2": { "firerate": 2.0, "radius": 1000, "damage": 15, "upgradecost": 20 }, 13 | "level3": { "firerate": 2.0, "radius": 1000, "damage": 40 } 14 | }, 15 | "shooty": { 16 | "cost": 50, 17 | "level1": { "firerate": 1.5, "radius": 48, "damage": 50, "upgradecost": 30 }, 18 | "level2": { "firerate": 1.5, "radius": 56, "damage": 100, "upgradecost": 60 }, 19 | "level3": { "firerate": 1.5, "radius": 64, "damage": 150 } 20 | }, 21 | "vortex": { 22 | "cost": 25, 23 | "level1": { "firerate": 2.5, "radius": 25, "damage": 0.6, "upgradecost": 30 }, 24 | "level2": { "firerate": 2.5, "radius": 30, "damage": 0.5, "upgradecost": 50 }, 25 | "level3": { "firerate": 2.5, "radius": 35, "damage": 0.4 } 26 | } 27 | }, 28 | "waves_stage1": { 29 | "totalwaves": 20, 30 | "wave1": { "img":"slime", "enter":0, "num": 6, "hp": 5, "spawnrate": 2, "speed": 0.2, "reward": 1 }, 31 | "wave2": { "img":"skelly", "enter":1, "num": 10, "hp": 20, "spawnrate": 2, "speed": 0.2, "reward": 1 }, 32 | "wave3": { "img":"cultist", "enter":3, "num": 20, "hp": 40, "spawnrate": 2, "speed": 0.2, "reward": 1 }, 33 | "wave4": { "img":"wisp", "enter":1, "num": 10, "hp": 55, "spawnrate": 2, "speed": 0.2, "reward": 1 }, 34 | "wave5": { "img":"knight", "enter":3, "num": 1, "hp": 130, "spawnrate": 2, "speed": 0.2, "reward": 10 }, 35 | 36 | "wave6": { "img":"skelly", "enter":1, "num": 10, "hp": 100, "spawnrate": 2, "speed": 0.2, "reward": 2 }, 37 | "wave7": { "img":"rat", "enter":0, "num": 30, "hp": 25, "spawnrate": 0.5, "speed": 0.8, "reward": 1 }, 38 | "wave8": { "img":"slime", "enter":3, "num": 20, "hp": 150, "spawnrate": 2, "speed": 0.2, "reward": 1 }, 39 | "wave9": { "img":"tree", "enter":4, "num": 20, "hp": 200, "spawnrate": 2, "speed": 0.2, "reward": 2 }, 40 | "wave10": { "img":"demon", "enter":3, "num": 1, "hp": 650, "spawnrate": 2, "speed": 0.2, "reward": 20 }, 41 | 42 | "wave11": { "img":"wisp", "enter":1, "num": 6, "hp": 250, "spawnrate": 2, "speed": 0.2, "reward": 3 }, 43 | "wave12": { "img":"skelly", "enter":0, "num": 10, "hp": 300, "spawnrate": 2, "speed": 0.2, "reward": 3 }, 44 | "wave13": { "img":"cultist", "enter":1, "num": 20, "hp": 350, "spawnrate": 2, "speed": 0.2, "reward": 3 }, 45 | "wave14": { "img":"slime", "enter":3, "num": 10, "hp": 400, "spawnrate": 2, "speed": 0.2, "reward": 3 }, 46 | "wave15": { "img":"knight", "enter":4, "num": 3, "hp": 1000, "spawnrate": 2, "speed": 0.2, "reward": 30 }, 47 | 48 | "wave16": { "img":"wisp", "enter":1, "num": 6, "hp": 700, "spawnrate": 2, "speed": 0.2, "reward": 8 }, 49 | "wave17": { "img":"rat", "enter":0, "num": 30, "hp": 100, "spawnrate": 0.5, "speed": 0.8, "reward": 10 }, 50 | "wave18": { "img":"tree", "enter":4, "num": 20, "hp": 1000, "spawnrate": 2, "speed": 0.2, "reward": 15 }, 51 | "wave19": { "img":"knight", "enter":3, "num": 10, "hp": 1500, "spawnrate": 2, "speed": 0.2, "reward": 25 }, 52 | "wave20": { "img":"demon", "enter":3, "num": 20, "hp": 3000, "spawnrate": 2, "speed": 0.2, "reward": 100 } 53 | }, 54 | "waves_stage2": { 55 | "totalwaves": 20, 56 | "wave1": { "img":"slime2", "enter":1, "num": 6, "hp": 5, "spawnrate": 2, "speed": 0.2, "reward": 1 }, 57 | "wave2": { "img":"skelly2", "enter":2, "num": 10, "hp": 20, "spawnrate": 2, "speed": 0.2, "reward": 1 }, 58 | "wave3": { "img":"cultist2", "enter":5, "num": 20, "hp": 40, "spawnrate": 2, "speed": 0.2, "reward": 1 }, 59 | "wave4": { "img":"wisp2", "enter":1, "num": 10, "hp": 55, "spawnrate": 2, "speed": 0.2, "reward": 1 }, 60 | "wave5": { "img":"knight2", "enter":2, "num": 1, "hp": 130, "spawnrate": 2, "speed": 0.2, "reward": 10 }, 61 | 62 | "wave6": { "img":"skelly2", "enter":2, "num": 10, "hp": 100, "spawnrate": 2, "speed": 0.2, "reward": 2 }, 63 | "wave7": { "img":"rat2", "enter":1, "num": 30, "hp": 25, "spawnrate": 0.5, "speed": 0.8, "reward": 1 }, 64 | "wave8": { "img":"slime2", "enter":2, "num": 20, "hp": 150, "spawnrate": 2, "speed": 0.2, "reward": 1 }, 65 | "wave9": { "img":"tree2", "enter":1, "num": 20, "hp": 200, "spawnrate": 2, "speed": 0.2, "reward": 2 }, 66 | "wave10": { "img":"demon2", "enter":2, "num": 1, "hp": 650, "spawnrate": 2, "speed": 0.2, "reward": 20 }, 67 | 68 | "wave11": { "img":"wisp2", "enter":2, "num": 6, "hp": 250, "spawnrate": 2, "speed": 0.2, "reward": 3 }, 69 | "wave12": { "img":"skelly2", "enter":1, "num": 10, "hp": 300, "spawnrate": 2, "speed": 0.2, "reward": 3 }, 70 | "wave13": { "img":"cultist2", "enter":2, "num": 20, "hp": 350, "spawnrate": 2, "speed": 0.2, "reward": 3 }, 71 | "wave14": { "img":"slime2", "enter":1, "num": 10, "hp": 400, "spawnrate": 2, "speed": 0.2, "reward": 3 }, 72 | "wave15": { "img":"knight2", "enter":2, "num": 3, "hp": 1000, "spawnrate": 2, "speed": 0.2, "reward": 30 }, 73 | 74 | "wave16": { "img":"wisp2", "enter":2, "num": 6, "hp": 700, "spawnrate": 2, "speed": 0.2, "reward": 8 }, 75 | "wave17": { "img":"rat2", "enter":1, "num": 30, "hp": 100, "spawnrate": 0.5, "speed": 0.8, "reward": 10 }, 76 | "wave18": { "img":"tree2", "enter":2, "num": 20, "hp": 1000, "spawnrate": 2, "speed": 0.2, "reward": 15 }, 77 | "wave19": { "img":"knight2", "enter":1, "num": 10, "hp": 1500, "spawnrate": 2, "speed": 0.2, "reward": 25 }, 78 | "wave20": { "img":"demon2", "enter":2, "num": 20, "hp": 3000, "spawnrate": 2, "speed": 0.2, "reward": 100 } 79 | }, 80 | "other_stage1": { 81 | "mapname": "stage1", 82 | "shooty_animation_time": 0.5, 83 | "beam_animation_time": 1.0, 84 | "vortex_animation_time": 1.0, 85 | "vortex_duration": 4.0, 86 | "laser_animation_time": 1, 87 | 88 | "player_health": 1, 89 | "startinggold": 5, 90 | 91 | "goalx": 9, 92 | "goaly": 3, 93 | "altendpointx": 12, 94 | "altendpointy": 4, 95 | "endpointx": 12, 96 | "endpointy": 4, 97 | 98 | "refundrate": 1, 99 | 100 | "enter0x": 6, "enter0y": 12, 101 | "enter1x": -1, "enter1y": 4, 102 | "enter2x": 18, "enter2y": 8, 103 | "enter3x": 3, "enter3y": -1 104 | }, 105 | "other_stage2": { 106 | "mapname": "stage2", 107 | "shooty_animation_time": 0.5, 108 | "beam_animation_time": 1.0, 109 | "vortex_animation_time": 1.0, 110 | "vortex_duration": 4.0, 111 | "laser_animation_time": 1, 112 | 113 | "player_health": 1, 114 | "startinggold": 5000, 115 | 116 | "goalx": 7, 117 | "goaly": 5, 118 | "altendpointx": 10, 119 | "altendpointy": 6, 120 | "endpointx": 6, 121 | "endpointy": 6, 122 | 123 | "refundrate": 1, 124 | 125 | "enter0x": -1, "enter0y": -1, 126 | "enter1x": -1, "enter1y": 4, 127 | "enter2x": 18, "enter2y": 8, 128 | "enter3x": -1, "enter3y": -1 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/hashagon/S.hx: -------------------------------------------------------------------------------- 1 | package hashagon; 2 | 3 | class S { 4 | /* Returns the ASCII value of the character. If character is a string, returns the ASCII 5 | * code of the first character in the string. */ 6 | public static inline function asciicode(character:String):Int { 7 | return character.charCodeAt(0); 8 | } 9 | 10 | /* Converts an ascii code to a string. E.g. fromascii(65) == "A" */ 11 | public static inline function fromascii(asciicode:Int):String { 12 | return String.fromCharCode(asciicode); 13 | } 14 | 15 | /* Joins up an array into a single string. */ 16 | public static inline function join(array:Array, seperator:String):String { 17 | return array.join(seperator); 18 | } 19 | 20 | /* Seperates a string into an array of strings. */ 21 | public static function seperate(currentstring:String, delimiter:String):Array { 22 | return currentstring.split(delimiter); 23 | } 24 | 25 | /** Returns an uppercase version of the string. */ 26 | public static inline function uppercase(currentstring:String):String { 27 | return currentstring.toUpperCase(); 28 | } 29 | 30 | /** Returns an lowercase version of the string. */ 31 | public static inline function lowercase(currentstring:String):String { 32 | return currentstring.toLowerCase(); 33 | } 34 | 35 | /** Splits a string into an array, divided by a given delimiter character (e.g. ",")*/ 36 | public static inline function split(currentstring:String, delimiter:String):Array { 37 | return currentstring.split(delimiter); 38 | } 39 | 40 | /** Removes substring from the fullstring. */ 41 | public static function removefromstring(fullstring:String, substring:String):String { 42 | var t:Int = positioninstring(fullstring, substring); 43 | if (t == -1) { 44 | return fullstring; 45 | }else { 46 | return removefromstring(getroot(fullstring, substring) + getbranch(fullstring, substring), substring); 47 | } 48 | } 49 | 50 | /** Returns true if the given stringtocheck is in the given fullstring. */ 51 | public static function isinstring(fullstring:String, stringtocheck:String):Bool { 52 | if (positioninstring(fullstring, stringtocheck) != -1) return true; 53 | return false; 54 | } 55 | 56 | /** Return the position of a substring in a given string. -1 if not found. */ 57 | public static inline function positioninstring(fullstring:String, substring:String, start:Int = 0):Int { 58 | return (fullstring.indexOf(substring, start)); 59 | } 60 | 61 | /** Return character at given position */ 62 | public static inline function letterat(currentstring:String, position:Int = 0):String { 63 | return currentstring.substr(position, 1); 64 | } 65 | 66 | /** Return characters from the middle of a string. */ 67 | public static inline function mid(currentstring:String, start:Int = 0, length:Int = 1):String { 68 | return currentstring.substr(start,length); 69 | } 70 | 71 | /** Return characters from the left of a string. */ 72 | public static inline function left(currentstring:String, length:Int = 1):String { 73 | return currentstring.substr(0,length); 74 | } 75 | 76 | /** Return characters from the right of a string. */ 77 | public static inline function right(currentstring:String, length:Int = 1):String { 78 | return currentstring.substr(currentstring.length - length, length); 79 | } 80 | 81 | /** Return string with N characters removed from the left. */ 82 | public static inline function removefromleft(currentstring:String, length:Int = 1):String { 83 | return right(currentstring, currentstring.length - length); 84 | } 85 | 86 | /** Return string with N characters removed from the right. */ 87 | public static inline function removefromright(currentstring:String, length:Int = 1):String { 88 | return left(currentstring, currentstring.length - length); 89 | } 90 | 91 | /** Reverse a string. */ 92 | public static function reversetext(currentstring:String):String { 93 | var reversedstring:String = ""; 94 | 95 | for (i in 0 ... currentstring.length) reversedstring += currentstring.substr(currentstring.length - i - 1, 1); 96 | return reversedstring; 97 | } 98 | 99 | /** Given a string currentstring, replace all occurances of string ch with ch2. Useful to remove characters. */ 100 | public static function replacechar(currentstring:String, ch:String = "|", ch2:String = ""):String { 101 | return StringTools.replace(currentstring, ch, ch2); 102 | } 103 | 104 | /** Given a string currentstring, return everything after the LAST occurance of the "ch" character */ 105 | public static function getlastbranch(currentstring:String, ch:String):String { 106 | var i:Int = currentstring.length - 1; 107 | while (i >= 0) { 108 | if (mid(currentstring, i, 1) == ch) { 109 | return mid(currentstring, i + 1, currentstring.length - i - 1); 110 | } 111 | i--; 112 | } 113 | return currentstring; 114 | } 115 | 116 | /** Given a string currentstring, return everything before the first occurance of the "ch" character */ 117 | public static function getroot(currentstring:String, ch:String):String { 118 | for (i in 0 ... currentstring.length) { 119 | if (mid(currentstring, i, 1) == ch) { 120 | return mid(currentstring, 0, i); 121 | } 122 | } 123 | return currentstring; 124 | } 125 | 126 | /** Given a string currentstring, return everything after the FIRST occurance of the "ch" character */ 127 | public static function getbranch(currentstring:String, ch:String):String { 128 | for (i in 0 ... currentstring.length) { 129 | if (mid(currentstring, i, 1) == ch) { 130 | return mid(currentstring, i + 1, currentstring.length - i - 1); 131 | } 132 | } 133 | return currentstring; 134 | } 135 | 136 | /** Given a string currentstring, return everything between the first and the last bracket (). */ 137 | public static function getbetweenbrackets(currentstring:String):String { 138 | while (mid(currentstring, 0, 1) != "(" && currentstring.length > 0) currentstring = mid(currentstring, 1, currentstring.length - 1); 139 | while (mid(currentstring, currentstring.length-1, 1) != ")" && currentstring.length > 0) currentstring = mid(currentstring, 0, currentstring.length - 1); 140 | 141 | if (currentstring.length <= 0) return ""; 142 | return mid(currentstring, 1, currentstring.length - 2); 143 | } 144 | 145 | /** Given a string currentstring, return a string without spaces around it. */ 146 | public static function trimspaces(currentstring:String):String { 147 | while (mid(currentstring, 0, 1) == " " && currentstring.length > 0) currentstring = mid(currentstring, 1, currentstring.length - 1); 148 | while (mid(currentstring, currentstring.length - 1, 1) == " " && currentstring.length > 0) currentstring = mid(currentstring, 0, currentstring.length - 1); 149 | 150 | while (mid(currentstring, 0, 1) == "\t" && currentstring.length > 0) currentstring = mid(currentstring, 1, currentstring.length - 1); 151 | while (mid(currentstring, currentstring.length - 1, 1) == "\t" && currentstring.length > 0) currentstring = mid(currentstring, 0, currentstring.length - 1); 152 | 153 | if (currentstring.length <= 0) return ""; 154 | return currentstring; 155 | } 156 | 157 | /** True if string currentstring is some kind of number; false if it's something else. */ 158 | public static function isnumber(currentstring:String):Bool { 159 | if (Math.isNaN(Std.parseFloat(currentstring))) { 160 | return false; 161 | }else{ 162 | return true; 163 | } 164 | return false; 165 | } 166 | } -------------------------------------------------------------------------------- /src/TowerCursor.hx: -------------------------------------------------------------------------------- 1 | import hxd.snd.WavData; 2 | import hashagon.*; 3 | import engine.*; 4 | 5 | class TowerCursor extends h2d.Object{ 6 | public function new(){ 7 | super(); 8 | //Three layered cursor that depends on context: the block below, 9 | //the tower below, and an icon. 10 | block = new h2d.Anim(Gfx.gettileset("ld46tiles").tiles, 0, this); 11 | icon = new h2d.Anim(Gfx.gettileset("towers").tiles, 0, this); 12 | tower = new h2d.Anim(Gfx.gettileset("towers").tiles, 0, this); 13 | 14 | rangedisplay = new h2d.Graphics(this); 15 | updaterangedisplay(-1); 16 | 17 | dropshadow = new h2d.Graphics(this); 18 | dropshadow.x = -12; 19 | dropshadow.y = -12; 20 | dropshadow.beginFill(Col.multiplylightness(Col.GREEN, 0.75)); 21 | dropshadow.drawRect(0, 0, 32, 12); 22 | dropshadow.endFill(); 23 | 24 | info = new h2d.Text(Game.numberfont, this); 25 | info.x = 5; 26 | info.y = -10; 27 | info.textAlign = Center; 28 | info.text = ""; 29 | 30 | block.visible = false; 31 | tower.visible = false; 32 | icon.visible = false; 33 | info.visible = false; 34 | dropshadow.visible = false; 35 | } 36 | 37 | public function canplacetower(mx:Int, my:Int, w:World):Bool{ 38 | if(w.canplacetower[w.contents[mx][my]]){ 39 | return true; 40 | } 41 | return false; 42 | } 43 | 44 | public function changeto(t:String, mx:Int, my:Int, w:World, toweratcursor:Entity){ 45 | hide(); 46 | 47 | x = (mx * w.tilewidth); 48 | y = (my * w.tileheight); 49 | block.x = 0; 50 | icon.x = 0; 51 | tower.x = 0; 52 | 53 | var towerisnull:Bool = false; 54 | var canplacetower:Bool = canplacetower(mx, my, w); 55 | if(toweratcursor == null){ 56 | towerisnull = true; 57 | }else{ 58 | if(toweratcursor.destroyed) towerisnull = true; 59 | } 60 | 61 | if(t == "sell"){ 62 | updaterangedisplay(-1); 63 | if(towerisnull){ 64 | //Don't show the block or the tower, just the icon 65 | icon.currentFrame = 47; icon.visible = true; 66 | icon.alpha = 0.4; 67 | }else if(toweratcursor.type == EntityType.GOAL){ 68 | icon.currentFrame = 47; icon.visible = true; 69 | icon.alpha = 0.4; 70 | }else{ 71 | //Don't show the block or the tower, just the icon 72 | icon.currentFrame = 47; icon.visible = true; 73 | icon.alpha = 0.8; 74 | 75 | info.text = "$" + Game.getrefundvalue(toweratcursor); 76 | info.textColor = Col.YELLOW; 77 | info.visible = true; 78 | dropshadow.visible = true; 79 | } 80 | }else{ 81 | if(towerisnull && canplacetower){ 82 | if(t == "laser"){ 83 | if(Game.gold < GameData.towers.laser.cost){ 84 | updaterangedisplay(-1); 85 | block.currentFrame = w.contents[mx][my]; block.visible = true; 86 | icon.currentFrame = 45; icon.visible = true; 87 | icon.alpha = 0.4; 88 | 89 | info.text = "$" + GameData.towers.laser.cost; 90 | info.textColor = Col.multiplylightness(Col.GREEN, 0.5); 91 | info.visible = true; 92 | dropshadow.visible = true; 93 | }else{ 94 | updaterangedisplay(GameData.towers.laser.level1.radius); 95 | block.currentFrame = w.contents[mx][my]; block.visible = true; 96 | icon.currentFrame = 44; icon.visible = true; 97 | icon.alpha = 0.4; 98 | } 99 | 100 | tower.currentFrame = 12; tower.visible = true; 101 | tower.alpha = 0.8; 102 | }else if(t == "beam"){ 103 | if(Game.gold < GameData.towers.beam.cost){ 104 | updaterangedisplay(-1); 105 | block.currentFrame = w.contents[mx][my]; block.visible = true; 106 | icon.currentFrame = 45; icon.visible = true; 107 | icon.alpha = 0.4; 108 | 109 | info.text = "$" + GameData.towers.beam.cost; 110 | info.textColor = Col.multiplylightness(Col.GREEN, 0.5); 111 | info.visible = true; 112 | dropshadow.visible = true; 113 | }else{ 114 | updaterangedisplay(-1); 115 | block.currentFrame = w.contents[mx][my]; block.visible = true; 116 | icon.currentFrame = 44; icon.visible = true; 117 | icon.alpha = 0.4; 118 | } 119 | 120 | var dir:Direction = Game.towerdirection(mx, my, w); 121 | switch(dir){ 122 | case Direction.LEFT: tower.currentFrame = 18; 123 | case Direction.RIGHT: tower.currentFrame = 24; 124 | case Direction.UP: tower.currentFrame = 30; 125 | case Direction.DOWN: tower.currentFrame = 36; 126 | } 127 | 128 | tower.visible = true; 129 | tower.alpha = 0.8; 130 | }else if(t == "shooty"){ 131 | if(Game.gold < GameData.towers.shooty.cost){ 132 | updaterangedisplay(-1); 133 | block.currentFrame = w.contents[mx][my]; block.visible = true; 134 | icon.currentFrame = 45; icon.visible = true; 135 | icon.alpha = 0.4; 136 | 137 | info.text = "$" + GameData.towers.shooty.cost; 138 | info.textColor = Col.multiplylightness(Col.GREEN, 0.5); 139 | info.visible = true; 140 | dropshadow.visible = true; 141 | }else{ 142 | updaterangedisplay(GameData.towers.shooty.level1.radius); 143 | block.currentFrame = w.contents[mx][my]; block.visible = true; 144 | icon.currentFrame = 44; icon.visible = true; 145 | icon.alpha = 0.4; 146 | } 147 | 148 | tower.currentFrame = 0; tower.visible = true; 149 | tower.alpha = 0.8; 150 | }else if(t == "vortex"){ 151 | if(Game.gold < GameData.towers.vortex.cost){ 152 | updaterangedisplay(-1); 153 | block.currentFrame = w.contents[mx][my]; block.visible = true; 154 | icon.currentFrame = 45; icon.visible = true; 155 | icon.alpha = 0.4; 156 | 157 | info.text = "$" + GameData.towers.vortex.cost; 158 | info.textColor = Col.multiplylightness(Col.GREEN, 0.5); 159 | info.visible = true; 160 | dropshadow.visible = true; 161 | }else{ 162 | updaterangedisplay(GameData.towers.vortex.level1.radius); 163 | block.currentFrame = w.contents[mx][my]; block.visible = true; 164 | icon.currentFrame = 44; icon.visible = true; 165 | icon.alpha = 0.4; 166 | } 167 | 168 | tower.currentFrame = 6; tower.visible = true; 169 | tower.alpha = 0.8; 170 | }else if(t == "upgrade"){ 171 | //Don't show the block or the tower, just the icon 172 | updaterangedisplay(-1); 173 | icon.currentFrame = 46; icon.visible = true; 174 | icon.alpha = 0.4; 175 | }else{ 176 | throw(t + " not found in TowerCursor"); 177 | } 178 | }else{ 179 | if(t == "laser"){ 180 | updaterangedisplay(-1); 181 | icon.currentFrame = 45; icon.visible = true; 182 | icon.alpha = 0.8; 183 | }else if(t == "beam"){ 184 | updaterangedisplay(-1); 185 | icon.currentFrame = 45; icon.visible = true; 186 | icon.alpha = 0.8; 187 | }else if(t == "shooty"){ 188 | updaterangedisplay(-1); 189 | icon.currentFrame = 45; icon.visible = true; 190 | icon.alpha = 0.8; 191 | }else if(t == "vortex"){ 192 | updaterangedisplay(-1); 193 | icon.currentFrame = 45; icon.visible = true; 194 | icon.alpha = 0.8; 195 | }else if(t == "upgrade"){ 196 | //Don't show the block or the tower, just the icon 197 | icon.currentFrame = 46; icon.visible = true; 198 | if(canplacetower){ 199 | if(toweratcursor.level >= 3){ 200 | updaterangedisplay(-1); 201 | icon.alpha = 0.4; 202 | 203 | info.text = "MAX"; 204 | info.textColor = Col.WHITE; 205 | info.visible = true; 206 | dropshadow.visible = true; 207 | }else{ 208 | /* Tried previewing upgrade radius changes, it just looked weird 209 | var upgradedrange:Float = -1; 210 | switch(toweratcursor.type){ 211 | case EntityType.TOWER_SHOOTY: 212 | if(toweratcursor.level == 1) upgradedrange = GameData.towers.shooty.level2.radius; 213 | if(toweratcursor.level == 2) upgradedrange = GameData.towers.shooty.level3.radius; 214 | case EntityType.TOWER_VORTEX: 215 | if(toweratcursor.level == 1) upgradedrange = GameData.towers.vortex.level2.radius; 216 | if(toweratcursor.level == 2) upgradedrange = GameData.towers.vortex.level3.radius; 217 | case EntityType.TOWER_LASER: 218 | if(toweratcursor.level == 1) upgradedrange = GameData.towers.laser.level2.radius; 219 | if(toweratcursor.level == 2) upgradedrange = GameData.towers.laser.level3.radius; 220 | default: 221 | } 222 | updaterangedisplay(upgradedrange, 0.5); */ 223 | updaterangedisplay(-1); 224 | icon.alpha = 0.8; 225 | 226 | info.text = "$" + Game.getupgradecost(toweratcursor); 227 | info.textColor = Col.WHITE; 228 | info.visible = true; 229 | dropshadow.visible = true; 230 | } 231 | }else{ 232 | updaterangedisplay(-1); 233 | icon.alpha = 0.4; 234 | } 235 | }else{ 236 | throw(t + " not found in TowerCursor"); 237 | } 238 | } 239 | } 240 | } 241 | 242 | public function hide(){ 243 | block.visible = false; 244 | tower.visible = false; 245 | icon.visible = false; 246 | info.visible = false; 247 | dropshadow.visible = false; 248 | updaterangedisplay(-1); 249 | } 250 | 251 | public function updaterangedisplay(radius:Float, alphamult:Float = 1.0){ 252 | if(radius <= 0){ 253 | rangedisplay.visible = false; 254 | }else{ 255 | rangedisplay.visible = true; 256 | rangedisplay.clear(); 257 | rangedisplay.moveTo(0, 0); 258 | rangedisplay.lineStyle(3, Col.WHITE, 0.3 * alphamult); 259 | rangedisplay.drawCircle(5, 5, radius); 260 | } 261 | } 262 | 263 | public var block:h2d.Anim; 264 | public var icon:h2d.Anim; 265 | public var tower:h2d.Anim; 266 | public var info:h2d.Text; 267 | public var rangedisplay:h2d.Graphics; 268 | public var dropshadow:h2d.Graphics; 269 | } -------------------------------------------------------------------------------- /src/scenes/TowerDefence.hx: -------------------------------------------------------------------------------- 1 | package scenes; 2 | 3 | import hashagon.*; 4 | import engine.*; 5 | import hxd.Key; 6 | 7 | class TowerDefence{ 8 | public static var firstcall:Bool = true; 9 | public static function init(){ 10 | if(firstcall){ 11 | firstcall = false; 12 | Game.initlayers(); 13 | } 14 | 15 | world = new World(); 16 | Game.reset(); 17 | Waves.init(); 18 | Waves.nextwave(); 19 | 20 | world.loadtiles("ld46tiles", 10, 10); 21 | 22 | for (i in 0 ... Gfx.numberoftiles(world.tileset)){ 23 | if(i > 5){ 24 | world.collidable[i] = true; 25 | } 26 | } 27 | world.setcanplacetower([6, 7, 8, 9, 10, 36, 37]); 28 | 29 | world.loadcsv(GameData.other.mapname); 30 | if(GameData.other.endpointx == GameData.other.altendpointx && 31 | GameData.other.endpointy == GameData.other.altendpointy){ 32 | world.getheatmap(GameData.other.endpointx, GameData.other.endpointy); 33 | }else{ 34 | world.getdoubleheatmap( 35 | GameData.other.endpointx, GameData.other.endpointy, 36 | GameData.other.altendpointx, GameData.other.altendpointy); 37 | } 38 | world.refreshmap(); 39 | 40 | world.monsters = []; 41 | world.towers = []; 42 | world.bullets = []; 43 | world.particles = []; 44 | 45 | setupnextwaveindicators(); 46 | inituielements(); 47 | 48 | world.towers.push(Entity.create(GameData.other.goalx, GameData.other.goaly, EntityType.GOAL, world)); 49 | 50 | timetillnextspawn = 0; 51 | } 52 | 53 | public static function update(){ 54 | var mx:Int = world.gridx(Mouse.x); 55 | var my:Int = world.gridy(Mouse.y); 56 | 57 | //Show tower cursor 58 | updatecursor(mx, my); 59 | 60 | if(Waves.enemiesleft <=0){ 61 | if(!Waves.finalwave()){ 62 | shownextwaveindicators(Waves.waves[Waves.currentwave+1].entrance); 63 | } 64 | }else{ 65 | shownextwaveindicators(-1); 66 | } 67 | 68 | animatenextwaveindicators(); 69 | 70 | //Call the nextwave once it's ready 71 | if(!Waves.finalwave()){ 72 | if(Waves.enemiesleft <=0){ 73 | //Check if all enemies are dead 74 | var enemiesalive:Bool = false; 75 | for(monsters in world.monsters){ 76 | if(!monsters.destroyed){ 77 | enemiesalive = true; 78 | break; 79 | } 80 | } 81 | if(!enemiesalive) Waves.nextwave(); 82 | } 83 | } 84 | if(Input.justpressed(Key.SPACE)){ 85 | 86 | } 87 | 88 | timetillnextspawn -= Core.deltatime; 89 | if(timetillnextspawn <= 0){ 90 | timetillnextspawn = Waves.spawnrate; 91 | if(Waves.enemiesleft > 0){ 92 | //Create a new enemy at the entrance! 93 | Game.createmonster(-1, 2, Waves.currenttype, Waves.enemyhealth, Waves.enemyspeed, world); 94 | Waves.enemiesleft--; 95 | } 96 | } 97 | 98 | world.render(); 99 | 100 | for(monster in world.monsters){ 101 | monster.update(); 102 | monster.render(); 103 | } 104 | 105 | for(tower in world.towers){ 106 | tower.update(); 107 | tower.render(); 108 | } 109 | 110 | for(bullet in world.bullets){ 111 | bullet.update(); 112 | bullet.render(); 113 | } 114 | 115 | for(particle in world.particles){ 116 | particle.update(); 117 | particle.render(); 118 | } 119 | 120 | //TO DO: clean up destroyed entities somewhere 121 | 122 | if(Input.justpressed(Key.NUMBER_1)){ 123 | Game.changeselectedmode(ButtonType.LASER); 124 | }else if(Input.justpressed(Key.NUMBER_2)){ 125 | Game.changeselectedmode(ButtonType.BEAM); 126 | }else if(Input.justpressed(Key.NUMBER_3)){ 127 | Game.changeselectedmode(ButtonType.SHOOTY); 128 | }else if(Input.justpressed(Key.NUMBER_4)){ 129 | Game.changeselectedmode(ButtonType.VORTEX); 130 | }else if(Input.justpressed(Key.NUMBER_5)){ 131 | Game.changeselectedmode(ButtonType.UPGRADE); 132 | }else if(Input.justpressed(Key.NUMBER_6)){ 133 | Game.changeselectedmode(ButtonType.SELL); 134 | } 135 | 136 | /* 137 | if(Input.justpressed(Key.P)){ 138 | QuickSave.quicksave(world); 139 | }else if(Input.justpressed(Key.L)){ 140 | QuickSave.quickload(world); 141 | }*/ 142 | 143 | //UI stuff 144 | Game.uipanel.updatecashdisplay(); 145 | Game.uipanel.updateallbuttons(); 146 | 147 | Game.updatetimers(); 148 | Game.playsounds_endframe(); 149 | } 150 | 151 | public static function cleanup(){ 152 | /* 153 | for(monster in world.monsters){ 154 | monster.destroy(); 155 | } 156 | 157 | for(tower in world.towers){ 158 | tower.destroy(); 159 | } 160 | 161 | for(bullet in world.bullets){ 162 | bullet.destroy(); 163 | } 164 | 165 | for(particle in world.particles){ 166 | particle.destroy(); 167 | } 168 | 169 | world.destroy();*/ 170 | } 171 | 172 | public static var world:World; 173 | 174 | public static var spawnrate:Float; 175 | public static var timetillnextspawn:Float; 176 | 177 | public static function inituielements(){ 178 | Game.cursormode = ButtonType.LASER; 179 | Game.uipanel.updateallbuttons(); 180 | 181 | towercursor = new TowerCursor(); 182 | Game.uilayer.addChild(towercursor); 183 | } 184 | 185 | public static function updatecursor(mx:Int, my:Int){ 186 | var toweratcursor:Entity = null; 187 | 188 | Game.uipanel.mouseover = true; 189 | if(Mouse.x < Game.uipanel.x) Game.uipanel.mouseover = false; 190 | 191 | if(Game.uipanel.mouseover || !Geom.inbox(Mouse.x, Mouse.y, 0, 0, Gfx.screenwidth, Gfx.screenheight)){ 192 | towercursor.hide(); 193 | }else{ 194 | for(t in world.towers){ 195 | if(!t.destroyed){ 196 | if(world.gridx(t.x) == mx && world.gridy(t.y) == my){ 197 | toweratcursor = t; 198 | break; 199 | } 200 | } 201 | } 202 | 203 | switch(Game.cursormode){ 204 | case ButtonType.SHOOTY: 205 | towercursor.changeto("shooty", mx, my, world, toweratcursor); 206 | case ButtonType.BEAM: 207 | towercursor.changeto("beam", mx, my, world, toweratcursor); 208 | case ButtonType.VORTEX: 209 | towercursor.changeto("vortex", mx, my, world, toweratcursor); 210 | case ButtonType.LASER: 211 | towercursor.changeto("laser", mx, my, world, toweratcursor); 212 | case ButtonType.UPGRADE: 213 | towercursor.changeto("upgrade", mx, my, world, toweratcursor); 214 | case ButtonType.SELL: 215 | towercursor.changeto("sell", mx, my, world, toweratcursor); 216 | } 217 | 218 | if(Mouse.leftclick()){ 219 | if(toweratcursor == null && towercursor.canplacetower(mx, my, world)){ 220 | var currenttowercost:Int = 0; 221 | if(Game.cursormode == ButtonType.SHOOTY){ 222 | currenttowercost = GameData.towers.shooty.cost; 223 | }else if(Game.cursormode == ButtonType.BEAM){ 224 | currenttowercost = GameData.towers.beam.cost; 225 | }else if(Game.cursormode == ButtonType.VORTEX){ 226 | currenttowercost = GameData.towers.vortex.cost; 227 | }else if(Game.cursormode == ButtonType.LASER){ 228 | currenttowercost = GameData.towers.laser.cost; 229 | } 230 | if(currenttowercost > 0){ 231 | Game.cost(currenttowercost, 232 | function(){ 233 | if(Game.cursormode == ButtonType.SHOOTY){ 234 | Game.createtower(mx, my, EntityType.TOWER_SHOOTY, world); 235 | }else if(Game.cursormode == ButtonType.BEAM){ 236 | Game.createtower(mx, my, EntityType.TOWER_BEAM, world); 237 | }else if(Game.cursormode == ButtonType.VORTEX){ 238 | Game.createtower(mx, my, EntityType.TOWER_VORTEX, world); 239 | }else if(Game.cursormode == ButtonType.LASER){ 240 | Game.createtower(mx, my, EntityType.TOWER_LASER, world); 241 | } 242 | }, function(){ 243 | 244 | }); 245 | } 246 | }else if(Game.cursormode == ButtonType.UPGRADE){ 247 | if(toweratcursor == null){ 248 | 249 | }else if(toweratcursor.level < 3){ 250 | var upgradetowercost:Int = Game.getupgradecost(toweratcursor); 251 | 252 | if(upgradetowercost > 0){ 253 | Game.cost(upgradetowercost, 254 | function(){ 255 | Game.upgradetower(toweratcursor); 256 | }, 257 | function(){ 258 | 259 | }); 260 | } 261 | } 262 | }else if(Game.cursormode == ButtonType.SELL){ 263 | if(toweratcursor != null){ 264 | if(toweratcursor.type != EntityType.GOAL){ 265 | Game.refundtower(toweratcursor); 266 | } 267 | } 268 | } 269 | } 270 | } 271 | } 272 | 273 | public static function shownextwaveindicators(num:Int){ 274 | if(num <= -1){ 275 | for(i in 0 ... 4) nextwaveindicators[i].visible = false; 276 | }else if(num < 4){ 277 | for(i in 0 ... 4) nextwaveindicators[i].visible = false; 278 | nextwaveindicators[num].visible = true; 279 | }else if(num == 4){ 280 | for(i in 0 ... 4) nextwaveindicators[i].visible = true; 281 | nextwaveindicators[2].visible = false; 282 | }else if(num == 5){ 283 | for(i in 0 ... 4) nextwaveindicators[i].visible = false; 284 | nextwaveindicators[1].visible = true; 285 | nextwaveindicators[2].visible = true; 286 | } 287 | } 288 | 289 | public static function animatenextwaveindicators(){ 290 | for(i in 0 ... 4){ 291 | if(nextwaveindicators[i].visible){ 292 | nextwaveindicators[i].alpha = ((Game.twoframe == 0)?0.5:1.0); 293 | } 294 | } 295 | } 296 | 297 | public static function setupnextwaveindicators(){ 298 | nextwaveindicators = []; 299 | var tileset:hashagon.displayobject.Tileset = Gfx.gettileset("nextindicate"); 300 | 301 | var nw:h2d.Anim = new h2d.Anim(tileset.tiles, 0, Game.backgroundlayer); 302 | nw.x = (GameData.other.enter0x * 10) - 5; 303 | nw.y = (GameData.other.enter0y - 2) * 10; 304 | nw.visible = false; 305 | nw.currentFrame = 0; nextwaveindicators.push(nw); 306 | 307 | nw = new h2d.Anim(tileset.tiles, 0, Game.backgroundlayer); 308 | nw.x = (GameData.other.enter1x + 1) * 10; 309 | nw.y = (GameData.other.enter1y * 10) - 10; 310 | nw.visible = false; 311 | nw.currentFrame = 3; nextwaveindicators.push(nw); 312 | 313 | nw = new h2d.Anim(tileset.tiles, 0, Game.backgroundlayer); 314 | nw.x = (GameData.other.enter2x - 2) * 10; 315 | nw.y = (GameData.other.enter2y * 10) - 10; 316 | nw.visible = false; 317 | nw.currentFrame = 2; nextwaveindicators.push(nw); 318 | 319 | nw = new h2d.Anim(tileset.tiles, 0, Game.backgroundlayer); 320 | nw.x = (GameData.other.enter3x * 10) - 5; 321 | nw.y = ((GameData.other.enter3y + 1) * 10) + 0; 322 | nw.visible = false; 323 | nw.currentFrame = 1; nextwaveindicators.push(nw); 324 | } 325 | 326 | public static var towercursor:TowerCursor; 327 | 328 | public static var nextwaveindicators:Array; 329 | } -------------------------------------------------------------------------------- /src/cherry/fs/ManifestFileSystem.hx: -------------------------------------------------------------------------------- 1 | package cherry.fs; 2 | 3 | import hxd.net.BinaryLoader; 4 | import hxd.impl.ArrayIterator; 5 | import hxd.fs.LoadedBitmap; 6 | import hxd.fs.NotFound; 7 | import hxd.fs.FileEntry; 8 | import hxd.fs.FileSystem; 9 | import haxe.io.Encoding; 10 | import haxe.io.Path; 11 | import haxe.io.Bytes; 12 | 13 | @:allow(cherry.fs.ManifestFileSystem) 14 | class ManifestEntry extends FileEntry 15 | { 16 | private var fs:ManifestFileSystem; 17 | private var relPath:String; 18 | 19 | private var isDir:Bool; 20 | private var contents:Array; 21 | 22 | private var file:String; 23 | private var originalFile:String; 24 | #if sys 25 | private var fio:sys.io.FileInput; 26 | #else 27 | private var bytes:Bytes; 28 | private var readPos:Int; 29 | private var loaded:Bool; 30 | #end 31 | 32 | public function new(fs:ManifestFileSystem, name:String, relPath:String, file:String, ?originalFile:String) 33 | { 34 | this.fs = fs; 35 | this.name = name; 36 | this.relPath = relPath; 37 | this.originalFile = originalFile; 38 | this.file = file; 39 | if (file == null) 40 | { 41 | isDir = true; 42 | contents = new Array(); 43 | } 44 | } 45 | 46 | override public function getSign():Int 47 | { 48 | #if sys 49 | var old = if( fio == null ) -1 else fio.tell(); 50 | open(); 51 | var i = fio.readInt32(); 52 | if( old < 0 ) close() else fio.seek(old, SeekBegin); 53 | return i; 54 | #else 55 | return bytes.get(0) | (bytes.get(1) << 8) | (bytes.get(2) << 16) | (bytes.get(3) << 24); 56 | #end 57 | } 58 | 59 | override public function getBytes():Bytes 60 | { 61 | #if sys 62 | return sys.io.File.getBytes(file); 63 | #else 64 | return bytes; 65 | #end 66 | } 67 | 68 | override public function open() 69 | { 70 | #if sys 71 | if (fio == null) 72 | fio = sys.io.File.read(file); 73 | else 74 | fio.seek(0, SeekBegin); 75 | #else 76 | readPos = 0; 77 | #end 78 | } 79 | 80 | override public function skip(nbytes:Int) 81 | { 82 | #if sys 83 | fio.seek(nbytes, SeekCur); 84 | #else 85 | readPos += nbytes; 86 | if (bytes.length < readPos) readPos = bytes.length; 87 | #end 88 | } 89 | 90 | override public function readByte():Int 91 | { 92 | #if sys 93 | return fio.readByte(); 94 | #else 95 | return bytes.get(readPos++); 96 | #end 97 | } 98 | 99 | override public function read(out:Bytes, pos:Int, size:Int) 100 | { 101 | #if sys 102 | fio.readFullBytes(out, pos, size); 103 | #else 104 | out.blit(pos, bytes, readPos, size); 105 | readPos += size; 106 | #end 107 | } 108 | 109 | override public function close() 110 | { 111 | #if sys 112 | if (fio != null) 113 | { 114 | fio.close(); 115 | fio = null; 116 | } 117 | #else 118 | readPos = 0; 119 | #end 120 | } 121 | 122 | public function fancyLoad(onReady:() -> Void, onProgress:(cur:Int, max:Int)->Void) 123 | { 124 | #if js 125 | if (loaded) 126 | { 127 | haxe.Timer.delay(onReady, 1); 128 | } 129 | else 130 | { 131 | var br:BinaryLoader = new BinaryLoader(file); 132 | br.onLoaded = (b) -> { loaded = true; bytes = b; onReady(); } 133 | br.onProgress = onProgress; 134 | br.load(); 135 | } 136 | #else 137 | load(onReady); 138 | #end 139 | } 140 | 141 | override public function load(?onReady:() -> Void) 142 | { 143 | #if macro 144 | onReady(); 145 | #elseif js 146 | if (loaded) 147 | { 148 | if (onReady != null) haxe.Timer.delay(onReady, 1); 149 | } 150 | else 151 | { 152 | js.Browser.window.fetch(file).then( 153 | (res:js.html.Response) -> { return res.arrayBuffer(); } 154 | ).then( 155 | (buf:js.lib.ArrayBuffer) -> { 156 | loaded = true; 157 | bytes = Bytes.ofData(buf); 158 | if (onReady != null) onReady(); 159 | } 160 | ); 161 | } 162 | #else 163 | if (onReady != null) haxe.Timer.delay(onReady, 1); 164 | #end 165 | } 166 | 167 | override public function loadBitmap(onLoaded:LoadedBitmap -> Void) 168 | { 169 | #if sys 170 | var bmp = new hxd.res.Image(this).toBitmap(); 171 | onLoaded(new hxd.fs.LoadedBitmap(bmp)); 172 | #elseif js 173 | load( () -> { 174 | var img:js.html.Image = new js.html.Image(); 175 | img.onload = (_) -> onLoaded(new LoadedBitmap(img)); 176 | img.src = file; 177 | }); 178 | #else 179 | throw "Unsupported platform"; 180 | #end 181 | } 182 | 183 | override public function exists(name:String):Bool 184 | { 185 | return _exists(name); 186 | } 187 | 188 | override public function get(name:String):FileEntry 189 | { 190 | return _get(name); 191 | } 192 | 193 | private function _exists(name:String):Bool 194 | { 195 | if (isDir) 196 | { 197 | for (c in contents) if (c.name == name) return true; 198 | } 199 | return false; 200 | } 201 | 202 | private function _get(name:String):ManifestEntry 203 | { 204 | if (isDir) 205 | { 206 | for (c in contents) if (c.name == name) return c; 207 | } 208 | return null; 209 | } 210 | 211 | override public function iterator():ArrayIterator 212 | { 213 | if (isDir) 214 | return new ArrayIterator(cast contents); 215 | return null; 216 | } 217 | 218 | #if !sys 219 | override private function get_isAvailable():Bool 220 | { 221 | return loaded; 222 | } 223 | #end 224 | 225 | override private function get_isDirectory():Bool 226 | { 227 | return isDir; 228 | } 229 | 230 | override private function get_path():String 231 | { 232 | return relPath == "." ? "" : relPath; 233 | } 234 | 235 | override private function get_size():Int 236 | { 237 | #if sys 238 | return sys.FileSystem.stat(file).size; 239 | #else 240 | return bytes != null ? bytes.length : 0; 241 | #end 242 | } 243 | 244 | private function dispose():Void 245 | { 246 | if (isDir) 247 | { 248 | for (c in contents) c.dispose(); 249 | contents = null; 250 | } 251 | #if sys 252 | close(); 253 | #else 254 | bytes = null; 255 | #end 256 | } 257 | 258 | } 259 | 260 | class ManifestFileSystem implements FileSystem { 261 | 262 | private var baseDir:String; 263 | public var manifest:Map; 264 | 265 | var root : ManifestEntry; 266 | 267 | public function new(dir:String, _manifest:Bytes) 268 | { 269 | this.baseDir = Path.addTrailingSlash(dir); 270 | this.root = new ManifestEntry(this, "", ".", null); 271 | 272 | this.manifest = new Map(); 273 | 274 | inline function insert(path:String, file:String, original:String):Void 275 | { 276 | var dir:Array = Path.directory(original).split('/'); 277 | var r:ManifestEntry = root; 278 | for (n in dir) 279 | { 280 | if (n == "") continue; 281 | var found:Bool = false; 282 | for (c in r.contents) 283 | { 284 | if (c.name == n) 285 | { 286 | r = c; 287 | found = true; 288 | break; 289 | } 290 | } 291 | if (!found) 292 | { 293 | var dirEntry = new ManifestEntry(this, n, r.relPath + "/" + n, null); 294 | r.contents.push(dirEntry); 295 | r = dirEntry; 296 | } 297 | } 298 | var entry:ManifestEntry = new ManifestEntry(this, Path.withoutDirectory(original), original, file, original); 299 | r.contents.push(entry); 300 | manifest.set(path, entry); 301 | } 302 | 303 | switch(_manifest.get(0)) 304 | { 305 | case 0: 306 | // binary 307 | throw "Binary manifest not yet supported!"; 308 | case 1: 309 | // serialized 310 | throw "Serialized manifest not yet supported!"; 311 | case 'm'.code: 312 | // id:path mapping 313 | throw "Mapping manifest not yet supported"; 314 | // var mapping:Array = _manifest.getString(2, _manifest.length - 2, Encoding.UTF8).split("\n"); 315 | // for (map in mapping) 316 | // { 317 | // var idx:Int = map.indexOf(":"); 318 | // insert(map.substr(0, idx), baseDir + map.substr(idx+1)); 319 | // } 320 | case 'l'.code: 321 | // path mapping 322 | throw "List manifest not yet supported"; 323 | // var mapping:Array = _manifest.getString(2, _manifest.length - 2, Encoding.UTF8).split("\n"); 324 | // for (path in mapping) 325 | // { 326 | // insert(path, baseDir + path); 327 | // } 328 | case '['.code: 329 | // JSON 330 | var json:Array<{ path:String, original:String }> = haxe.Json.parse(_manifest.toString()); 331 | for (entry in json) 332 | { 333 | insert(entry.path, baseDir + entry.path, entry.original); 334 | } 335 | } 336 | } 337 | 338 | public function getRoot():FileEntry 339 | { 340 | return root; 341 | } 342 | 343 | private function splitPath(path:String) 344 | { 345 | return path == "." ? [] : path.split("/"); 346 | } 347 | 348 | private function find(path:String):ManifestEntry 349 | { 350 | var r = root; 351 | for (p in splitPath(path)) 352 | { 353 | r = r._get(p); 354 | if (r == null) return null; 355 | } 356 | return r; 357 | } 358 | 359 | public function exists( path : String ) { 360 | return find(path) != null; 361 | } 362 | 363 | public function get(path:String) 364 | { 365 | var entry:ManifestEntry = find(path); 366 | if (entry == null) throw new NotFound(path); 367 | return entry; 368 | } 369 | 370 | public function dispose() { 371 | root.dispose(); 372 | root = null; 373 | } 374 | 375 | public function dir(path:String) : Array { 376 | var entry:ManifestEntry = find(path); 377 | if (entry == null) throw new NotFound(path); 378 | return cast entry.contents.copy(); 379 | } 380 | 381 | } 382 | 383 | /* 384 | 385 | using haxe.io.Path; 386 | 387 | class BytesFileEntry extends FileEntry { 388 | 389 | var fullPath : String; 390 | var bytes : haxe.io.Bytes; 391 | var pos : Int; 392 | 393 | public function new(path, bytes) { 394 | this.fullPath = path; 395 | this.name = path.split("/").pop(); 396 | this.bytes = bytes; 397 | } 398 | 399 | override function get_path() { 400 | return fullPath; 401 | } 402 | 403 | override function getSign() : Int { 404 | return bytes.get(0) | (bytes.get(1) << 8) | (bytes.get(2) << 16) | (bytes.get(3) << 24); 405 | } 406 | 407 | override function getBytes() : haxe.io.Bytes { 408 | return bytes; 409 | } 410 | 411 | override function open() { 412 | pos = 0; 413 | } 414 | 415 | override function skip( nbytes : Int ) { 416 | pos += nbytes; 417 | } 418 | override function readByte() : Int { 419 | return bytes.get(pos++); 420 | } 421 | 422 | override function read( out : haxe.io.Bytes, pos : Int, size : Int ) { 423 | out.blit(pos, bytes, this.pos, size); 424 | this.pos += size; 425 | } 426 | 427 | override function close() { 428 | } 429 | 430 | override function load( ?onReady : Void -> Void ) : Void { 431 | haxe.Timer.delay(onReady, 1); 432 | } 433 | 434 | override function loadBitmap( onLoaded : LoadedBitmap -> Void ) : Void { 435 | #if flash 436 | var loader = new flash.display.Loader(); 437 | loader.contentLoaderInfo.addEventListener(flash.events.IOErrorEvent.IO_ERROR, function(e:flash.events.IOErrorEvent) { 438 | throw Std.string(e) + " while loading " + fullPath; 439 | }); 440 | loader.contentLoaderInfo.addEventListener(flash.events.Event.COMPLETE, function(_) { 441 | var content : flash.display.Bitmap = cast loader.content; 442 | onLoaded(new hxd.fs.LoadedBitmap(content.bitmapData)); 443 | loader.unload(); 444 | }); 445 | loader.loadBytes(bytes.getData()); 446 | #elseif js 447 | var mime = switch fullPath.extension().toLowerCase() { 448 | case 'jpg' | 'jpeg': 'image/jpeg'; 449 | case 'png': 'image/png'; 450 | case 'gif': 'image/gif'; 451 | case _: throw 'Cannot determine image encoding, try adding an extension to the resource path'; 452 | } 453 | var img = new js.html.Image(); 454 | img.onload = function() onLoaded(new hxd.fs.LoadedBitmap(img)); 455 | img.src = 'data:$mime;base64,' + haxe.crypto.Base64.encode(bytes); 456 | #else 457 | throw "Not implemented"; 458 | #end 459 | } 460 | 461 | override function exists( name : String ) : Bool return false; 462 | override function get( name : String ) : FileEntry return null; 463 | 464 | override function iterator() : hxd.impl.ArrayIterator return new hxd.impl.ArrayIterator(new Array()); 465 | 466 | override function get_size() return bytes.length; 467 | 468 | } 469 | 470 | class BytesFileSystem implements FileSystem { 471 | 472 | 473 | }*/ -------------------------------------------------------------------------------- /src/Game.hx: -------------------------------------------------------------------------------- 1 | import haxe.Constraints.Function; 2 | import hashagon.*; 3 | import hashagon.displayobject.*; 4 | import engine.*; 5 | import motion.Actuate; 6 | import motion.easing.*; 7 | 8 | class Game{ 9 | public static function loadfonts(){ 10 | textfont = hxd.Res.pressstart.toFont(); 11 | numberfont = hxd.Res.pressstart.toFont(); 12 | smallfont = hxd.Res.pixelzim.toFont(); 13 | } 14 | public static var textfont:h2d.Font; 15 | public static var numberfont:h2d.Font; 16 | public static var smallfont:h2d.Font; 17 | 18 | public static function reset(){ 19 | maxhp = GameData.other.player_health; 20 | hp = maxhp; 21 | 22 | gold = GameData.other.startinggold; 23 | Game.uipanel.updatecashdisplay(); 24 | 25 | leveltime = 0; 26 | twoframe = 0; 27 | twoframeslow = 0; 28 | speed = 1.0; 29 | soundsthisframe = []; 30 | } 31 | 32 | public static var hp:Int; 33 | public static var maxhp:Int; 34 | public static var gold:Int; 35 | 36 | public static function getrefundvalue(toweratcursor:Entity):Int{ 37 | var refund:Int = 0; 38 | if(toweratcursor.type == EntityType.TOWER_SHOOTY){ 39 | refund += GameData.towers.shooty.cost; 40 | if(toweratcursor.level >= 2){ 41 | refund += GameData.towers.shooty.level1.upgradecost; 42 | } 43 | if(toweratcursor.level >= 3){ 44 | refund += GameData.towers.shooty.level2.upgradecost; 45 | } 46 | }else if(toweratcursor.type == EntityType.TOWER_BEAM){ 47 | refund += GameData.towers.beam.cost; 48 | if(toweratcursor.level >= 2){ 49 | refund += GameData.towers.beam.level1.upgradecost; 50 | } 51 | if(toweratcursor.level >= 3){ 52 | refund += GameData.towers.beam.level2.upgradecost; 53 | } 54 | }else if(toweratcursor.type == EntityType.TOWER_VORTEX){ 55 | refund += GameData.towers.vortex.cost; 56 | if(toweratcursor.level >= 2){ 57 | refund += GameData.towers.vortex.level1.upgradecost; 58 | } 59 | if(toweratcursor.level >= 3){ 60 | refund += GameData.towers.vortex.level2.upgradecost; 61 | } 62 | }else if(toweratcursor.type == EntityType.TOWER_LASER){ 63 | refund += GameData.towers.laser.cost; 64 | if(toweratcursor.level >= 2){ 65 | refund += GameData.towers.laser.level1.upgradecost; 66 | } 67 | if(toweratcursor.level >= 3){ 68 | refund += GameData.towers.laser.level2.upgradecost; 69 | } 70 | } 71 | 72 | refund = Std.int(refund * GameData.other.refundrate); 73 | if(refund < 1) refund = 1; 74 | return refund; 75 | } 76 | 77 | public static function getupgradecost(toweratcursor:Entity):Int{ 78 | var upgradetowercost:Int = 0; 79 | if(toweratcursor.type == EntityType.TOWER_SHOOTY){ 80 | if(toweratcursor.level == 1) upgradetowercost = GameData.towers.shooty.level1.upgradecost; 81 | if(toweratcursor.level == 2) upgradetowercost = GameData.towers.shooty.level2.upgradecost; 82 | }else if(toweratcursor.type == EntityType.TOWER_BEAM){ 83 | if(toweratcursor.level == 1) upgradetowercost = GameData.towers.beam.level1.upgradecost; 84 | if(toweratcursor.level == 2) upgradetowercost = GameData.towers.beam.level2.upgradecost; 85 | }else if(toweratcursor.type == EntityType.TOWER_VORTEX){ 86 | if(toweratcursor.level == 1) upgradetowercost = GameData.towers.vortex.level1.upgradecost; 87 | if(toweratcursor.level == 2) upgradetowercost = GameData.towers.vortex.level2.upgradecost; 88 | }else if(toweratcursor.type == EntityType.TOWER_LASER){ 89 | if(toweratcursor.level == 1) upgradetowercost = GameData.towers.laser.level1.upgradecost; 90 | if(toweratcursor.level == 2) upgradetowercost = GameData.towers.laser.level2.upgradecost; 91 | } 92 | return upgradetowercost; 93 | } 94 | 95 | public static function refundtower(toweratcursor:Entity){ 96 | gold += getrefundvalue(toweratcursor); 97 | Game.playsound("sell"); 98 | toweratcursor.destroy(); 99 | } 100 | 101 | public static function upgradetower(tower:Entity){ 102 | if(tower.type == EntityType.TOWER_SHOOTY){ 103 | if(tower.level == 1){ 104 | tower.baseframe += 2; 105 | tower.bulletdamage = GameData.towers.shooty.level2.damage; 106 | tower.targetradius = GameData.towers.shooty.level2.radius; 107 | tower.level = 2; 108 | 109 | tower.updatetowerradius(); 110 | Game.playsound("upgrade"); 111 | }else if(tower.level == 2){ 112 | tower.baseframe += 2; 113 | tower.bulletdamage = GameData.towers.shooty.level3.damage; 114 | tower.targetradius = GameData.towers.shooty.level3.radius; 115 | tower.level = 3; 116 | 117 | tower.updatetowerradius(); 118 | Game.playsound("upgrade"); 119 | } 120 | }else if(tower.type == EntityType.TOWER_BEAM){ 121 | if(tower.level == 1){ 122 | tower.baseframe+=2; 123 | tower.bulletdamage = GameData.towers.beam.level2.damage; 124 | tower.targetradius = GameData.towers.beam.level2.radius; 125 | tower.level = 2; 126 | 127 | tower.updatetowerradius(); 128 | Game.playsound("upgrade"); 129 | }else if(tower.level == 2){ 130 | tower.baseframe += 2; 131 | tower.bulletdamage = GameData.towers.beam.level3.damage; 132 | tower.targetradius = GameData.towers.beam.level3.radius; 133 | tower.level = 3; 134 | 135 | tower.updatetowerradius(); 136 | Game.playsound("upgrade"); 137 | } 138 | }else if(tower.type == EntityType.TOWER_VORTEX){ 139 | if(tower.level == 1){ 140 | tower.baseframe += 2; 141 | tower.bulletdamage = GameData.towers.vortex.level2.damage; 142 | tower.targetradius = GameData.towers.vortex.level2.radius; 143 | tower.level = 2; 144 | 145 | tower.updatetowerradius(); 146 | Game.playsound("upgrade"); 147 | }else if(tower.level == 2){ 148 | tower.baseframe += 2; 149 | tower.bulletdamage = GameData.towers.vortex.level3.damage; 150 | tower.targetradius = GameData.towers.vortex.level3.radius; 151 | tower.level = 3; 152 | 153 | tower.updatetowerradius(); 154 | Game.playsound("upgrade"); 155 | } 156 | }else if(tower.type == EntityType.TOWER_LASER){ 157 | if(tower.level == 1){ 158 | tower.baseframe += 2; 159 | tower.bulletdamage = GameData.towers.laser.level2.damage; 160 | tower.targetradius = GameData.towers.laser.level2.radius; 161 | tower.level = 2; 162 | 163 | tower.updatetowerradius(); 164 | Game.playsound("upgrade"); 165 | }else if(tower.level == 2){ 166 | tower.baseframe += 2; 167 | tower.bulletdamage = GameData.towers.laser.level3.damage; 168 | tower.targetradius = GameData.towers.laser.level3.radius; 169 | tower.level = 3; 170 | 171 | tower.updatetowerradius(); 172 | Game.playsound("upgrade"); 173 | } 174 | } 175 | } 176 | 177 | public static function towerdirection(x:Int, y:Int, w:World):Direction{ 178 | //Kludged in particular squares in the final map: 179 | if(x == 14 && y == 7) return Direction.LEFT; 180 | if(x == 12 && y == 1) return Direction.DOWN; 181 | if(y == 11) return Direction.UP; 182 | if(y == 2) return Direction.RIGHT; 183 | if(x == 1 && y == 6) return Direction.RIGHT; 184 | if(x == 9 && y == 9) return Direction.RIGHT; 185 | 186 | //Otherwise: 187 | if(w.heatat(x - 1, y, false) < 10000){ 188 | return Direction.LEFT; 189 | }else if(w.heatat(x + 1, y, false) < 10000){ 190 | return Direction.RIGHT; 191 | }else if(w.heatat(x, y - 1, false) < 10000){ 192 | return Direction.UP; 193 | }else if(w.heatat(x, y + 1, false) < 10000){ 194 | return Direction.DOWN; 195 | } 196 | return Direction.LEFT; 197 | } 198 | 199 | public static function createtower(x:Int, y:Int, type:EntityType, w:World, lvl:Int = 1){ 200 | var newtower:Entity = Entity.create(x, y, type, w); 201 | if(lvl == 2){ 202 | upgradetower(newtower); 203 | }else if(lvl == 3){ 204 | upgradetower(newtower); 205 | upgradetower(newtower); 206 | } 207 | Game.playsound("place"); 208 | 209 | w.towers.push(newtower); 210 | } 211 | 212 | public static function createmonster(x:Int, y:Int, type:Int, hp:Int, speed:Float, w:World){ 213 | var enemy:Entity = null; 214 | 215 | var enterx:Int = GameData.other.enter0x; 216 | var entery:Int = GameData.other.enter0y; 217 | 218 | if(Waves.entrance == 0){ 219 | enterx = GameData.other.enter0x; 220 | entery = GameData.other.enter0y; 221 | }else if(Waves.entrance == 1){ 222 | enterx = GameData.other.enter1x; 223 | entery = GameData.other.enter1y; 224 | }else if(Waves.entrance == 2){ 225 | enterx = GameData.other.enter2x; 226 | entery = GameData.other.enter2y; 227 | }else if(Waves.entrance == 3){ 228 | enterx = GameData.other.enter3x; 229 | entery = GameData.other.enter3y; 230 | }else if(Waves.entrance == 4){ 231 | if(Waves.enemiesleft % 3 == 0){ 232 | enterx = GameData.other.enter0x; 233 | entery = GameData.other.enter0y; 234 | }else if(Waves.enemiesleft % 3 == 1){ 235 | enterx = GameData.other.enter1x; 236 | entery = GameData.other.enter1y; 237 | }else if(Waves.enemiesleft % 3 == 2){ 238 | enterx = GameData.other.enter3x; 239 | entery = GameData.other.enter3y; 240 | } 241 | }else if(Waves.entrance == 5){ 242 | if(Waves.enemiesleft % 2 == 0){ 243 | enterx = GameData.other.enter1x; 244 | entery = GameData.other.enter1y; 245 | }else if(Waves.enemiesleft % 2 == 1){ 246 | enterx = GameData.other.enter2x; 247 | entery = GameData.other.enter2y; 248 | } 249 | } 250 | 251 | enemy = Entity.create(enterx, entery, EntityType.ENEMY, w); 252 | 253 | enemy.maxhp = hp; 254 | enemy.hp = enemy.maxhp; 255 | enemy.baseframe = type; 256 | enemy.speed = speed; 257 | 258 | w.monsters.push(enemy); 259 | } 260 | 261 | public static function createvortex(tower:Entity){ 262 | var w:World = tower.world; 263 | 264 | var newvortex:Entity = Entity.create(w.gridx(tower.x), w.gridy(tower.y), EntityType.VORTEX, w); 265 | newvortex.x = tower.x; 266 | newvortex.y = tower.y; 267 | newvortex.targetradius = tower.targetradius; 268 | 269 | Game.playsound("vortex"); 270 | newvortex.updatevortex(1.0); 271 | bulletlayer.addChild(newvortex.primative); 272 | 273 | //Apply slowdown effect to all enemies in the vortex 274 | for(monster in w.monsters){ 275 | if(Geom.distance(monster.x, monster.y, newvortex.x, newvortex.y) <= newvortex.targetradius){ 276 | monster.slowenemy(tower.bulletdamage, GameData.other.vortex_duration); 277 | } 278 | } 279 | 280 | Actuate.tween(newvortex, GameData.other.vortex_animation_time, {animpercent: 1.0}) 281 | .ease(Sine.easeIn) 282 | .onUpdate(function(){ 283 | newvortex.updatevortex(1 - newvortex.animpercent); 284 | }) 285 | .onComplete(function(){ 286 | //Destory the vortex 287 | newvortex.destroy(); 288 | }); 289 | 290 | w.bullets.push(newvortex); 291 | } 292 | 293 | public static function createbeam(tower:Entity){ 294 | var w:World = tower.world; 295 | 296 | var newbeam:Entity = Entity.create(w.gridx(tower.x), w.gridy(tower.y), EntityType.BEAM, w); 297 | //Super weid thing here: newbeam.x/y returns NaN in javascript. Reassigning 298 | //the values here fixes it, somehow 299 | newbeam.x = tower.x; 300 | newbeam.y = tower.y; 301 | newbeam.direction = tower.direction; 302 | 303 | switch(newbeam.direction){ 304 | case Direction.LEFT: 305 | newbeam.sprite = new h2d.Anim([Gfx.getimage("beam_horizontal_left")], 0); 306 | newbeam.sprite.x = newbeam.x - Gfx.screenwidth; 307 | newbeam.sprite.y = newbeam.y + 2; 308 | case Direction.RIGHT: 309 | newbeam.sprite = new h2d.Anim([Gfx.getimage("beam_horizontal_right")], 0); 310 | newbeam.sprite.x = newbeam.x + 10; 311 | newbeam.sprite.y = newbeam.y + 2; 312 | case Direction.UP: 313 | newbeam.sprite = new h2d.Anim([Gfx.getimage("beam_vertical_up")], 0); 314 | newbeam.sprite.x = newbeam.x + 3; 315 | newbeam.sprite.y = newbeam.y - Gfx.screenheight; 316 | case Direction.DOWN: 317 | newbeam.sprite = new h2d.Anim([Gfx.getimage("beam_vertical_down")], 0); 318 | newbeam.sprite.x = newbeam.x + 3; 319 | newbeam.sprite.y = newbeam.y + 10; 320 | } 321 | 322 | bulletlayer.addChild(newbeam.sprite); 323 | newbeam.updatebeam(1.0); 324 | 325 | //Beams damage all enemies in path instantously when created: 326 | var beamposition:Int; 327 | 328 | switch(newbeam.direction){ 329 | case Direction.LEFT, Direction.RIGHT: 330 | //Horizontal Beams 331 | beamposition = w.gridy(tower.y); 332 | for(monster in w.monsters){ 333 | if(w.gridy(monster.y) == beamposition){ 334 | //Damage the enemy 335 | monster.damageenemy(tower.bulletdamage); 336 | } 337 | } 338 | case Direction.UP, Direction.DOWN: 339 | //Vertical Beams 340 | beamposition = w.gridy(tower.x); 341 | for(monster in w.monsters){ 342 | if(w.gridy(monster.x) == beamposition){ 343 | //Damage the enemy 344 | monster.damageenemy(tower.bulletdamage); 345 | } 346 | } 347 | } 348 | 349 | Game.playsound("beam"); 350 | 351 | Actuate.tween(newbeam, GameData.other.beam_animation_time, {animpercent: 1.0}) 352 | .ease(Sine.easeIn) 353 | .onUpdate(function(){ 354 | newbeam.updatebeam(1 - newbeam.animpercent); 355 | }) 356 | .onComplete(function(){ 357 | //Destory the beam 358 | newbeam.destroy(); 359 | }); 360 | 361 | w.bullets.push(newbeam); 362 | } 363 | 364 | public static function createbullet(tower:Entity, monster:Entity){ 365 | var w:World = tower.world; 366 | 367 | var newbullet:Entity = Entity.create(w.gridx(tower.x), w.gridy(tower.y), EntityType.BULLET, w); 368 | 369 | Game.playsound("mine_launch"); 370 | Actuate.tween(newbullet, GameData.other.shooty_animation_time, {animpercent: 1.0}) 371 | .onUpdate(function(){ 372 | //start point tower, end point monster 373 | //x: monster.x, y: monster.y 374 | newbullet.x = Geom.lerp(tower.x, monster.x, newbullet.animpercent); 375 | newbullet.y = Geom.lerp(tower.y, monster.y, newbullet.animpercent); 376 | }) 377 | .ease(Back.easeIn) 378 | .onComplete(function(){ 379 | Game.playsound("mine_land"); 380 | //Destory the bullet 381 | newbullet.destroy(); 382 | //Damage the enemy 383 | monster.damageenemy(tower.bulletdamage); 384 | }); 385 | 386 | w.bullets.push(newbullet); 387 | } 388 | 389 | public static function createlaser(tower:Entity, monster:Entity){ 390 | var w:World = tower.world; 391 | 392 | var newlaser:Entity = Entity.create(w.gridx(tower.x), w.gridy(tower.y), EntityType.LASER, w); 393 | newlaser.x = tower.x; 394 | newlaser.y = tower.y; 395 | newlaser.endx = newlaser.x; 396 | newlaser.endy = newlaser.y; 397 | bulletlayer.addChild(newlaser.primative); 398 | 399 | //Damage the enemy immediately 400 | monster.damageenemy(tower.bulletdamage); 401 | playsound("laser"); 402 | 403 | Actuate.tween(newlaser, GameData.other.laser_animation_time, {animpercent: 1.0}) 404 | .onUpdate(function(){ 405 | newlaser.endx = monster.x; 406 | newlaser.endy = monster.y; 407 | newlaser.updatelaser(1 - newlaser.animpercent); 408 | }) 409 | .onComplete(function(){ 410 | //Destory the laser 411 | newlaser.destroy(); 412 | }); 413 | 414 | w.bullets.push(newlaser); 415 | } 416 | 417 | /* Pick a target for tower based on range etc */ 418 | public static function picktarget(tower:Entity) { 419 | var possibletargets:Array = []; 420 | 421 | for(monster in tower.world.monsters){ 422 | if(!monster.destroyed){ 423 | if(Geom.distance( 424 | monster.x + monster.centerx, monster.y + monster.centery, 425 | tower.x + tower.centerx, tower.y + tower.centery) <= tower.targetradius){ 426 | possibletargets.push(monster); 427 | break; //Let's literally just take the first one and see how well that works out 428 | } 429 | } 430 | } 431 | 432 | if(possibletargets.length > 0){ 433 | tower.targetentity = Random.pick(possibletargets); 434 | }else{ 435 | tower.targetentity = null; 436 | } 437 | } 438 | 439 | public static function initlayers(){ 440 | //Let's set up some layers to easily control draw order! 441 | backgroundlayer = new h2d.Object(); 442 | monsterlayer = new h2d.Object(); 443 | towerlayer = new h2d.Object(); 444 | bulletlayer = new h2d.Object(); 445 | uilayer = new h2d.Object(); 446 | waveprogress = new WaveProgress(); 447 | uipanel = new UIPanel(); 448 | 449 | Gfx.core.s2d.addChild(Game.backgroundlayer); 450 | Gfx.core.s2d.addChild(Game.towerlayer); 451 | Gfx.core.s2d.addChild(Game.monsterlayer); 452 | Gfx.core.s2d.addChild(Game.bulletlayer); 453 | Gfx.core.s2d.addChild(waveprogress); 454 | Gfx.core.s2d.addChild(uipanel); 455 | Gfx.core.s2d.addChild(Game.uilayer); 456 | } 457 | 458 | public static function monsterreachestheend(monster:Entity){ 459 | hp--; 460 | monster.destroy(); 461 | 462 | if(hp <= 0){ 463 | Scene.change("gameover"); 464 | } 465 | } 466 | 467 | public static function cost(g:Int, success:Function, fail:Function){ 468 | if(gold >= g){ 469 | gold -= g; 470 | success(); 471 | }else{ 472 | fail(); 473 | } 474 | } 475 | 476 | public static function updatetimers(){ 477 | leveltime += hxd.Timer.dt; 478 | 479 | twoframe = (((leveltime * 1000) % 400 > 200)?1:0); 480 | twoframeslow = (((leveltime * 1000) % 800 > 400)?1:0); 481 | } 482 | 483 | public static function changeselectedmode(type:ButtonType){ 484 | cursormode = type; 485 | Game.playsound("button"); 486 | } 487 | 488 | public static function playsound(sfx:String){ 489 | if(soundsthisframe.indexOf(sfx) == -1) soundsthisframe.push(sfx); 490 | } 491 | 492 | public static function playsounds_endframe(){ 493 | for(sounds in soundsthisframe){ 494 | Sound.play(sounds); 495 | } 496 | soundsthisframe = []; 497 | } 498 | 499 | public static var soundsthisframe:Array; 500 | 501 | public static var twoframe:Int; 502 | public static var twoframeslow:Int; 503 | 504 | public static var backgroundlayer:h2d.Object; 505 | public static var monsterlayer:h2d.Object; 506 | public static var bulletlayer:h2d.Object; 507 | public static var towerlayer:h2d.Object; 508 | public static var uilayer:h2d.Object; 509 | public static var uipanel:UIPanel; 510 | public static var waveprogress:WaveProgress; 511 | 512 | public static var leveltime:Float; 513 | 514 | public static var cursormode:ButtonType; 515 | 516 | public static var speed:Float; 517 | } -------------------------------------------------------------------------------- /src/engine/Entity.hx: -------------------------------------------------------------------------------- 1 | package engine; 2 | 3 | import hashagon.*; 4 | import hashagon.displayobject.*; 5 | import motion.*; 6 | 7 | class Entity{ 8 | //Static functions 9 | public static function create(x:Int, y:Int, type:EntityType, w:World):Entity{ 10 | var e:Entity = new Entity(); 11 | e.x = x * w.tilewidth; 12 | e.y = y * w.tileheight; 13 | e.type = type; 14 | e.world = w; 15 | e.inittype(); 16 | 17 | //Almost everything is a tile in size 18 | e.centerx = w.tilewidth / 2; 19 | e.centery = w.tileheight / 2; 20 | 21 | e.destroyed = false; 22 | return e; 23 | } 24 | 25 | //Class functions 26 | public function new(){ 27 | sprite = null; 28 | primative = null; 29 | 30 | baseframe = 0; 31 | offsetframe = 0; 32 | animpercent = 0; 33 | x = 0; 34 | y = 0; 35 | 36 | speedmultiplier = 1.0; 37 | speeddampen = 0; 38 | 39 | //Tower stuff 40 | targetradius = 0; 41 | firerate = 0; 42 | timetillnextshot = 0; 43 | bulletdamage = 0; 44 | } 45 | 46 | public function updatebeam(power:Float){ 47 | sprite.alpha = 0.8 * power; 48 | } 49 | 50 | public function updatevortex(power:Float){ 51 | primative.clear(); 52 | primative.x = x; 53 | primative.y = y; 54 | 55 | primative.moveTo(0, 0); 56 | //Outer ring 57 | primative.beginFill(Col.MAGENTA, 0.1 * power); 58 | primative.drawCircle(world.tilewidth / 2, world.tileheight / 2, targetradius); 59 | primative.endFill(); 60 | primative.lineStyle(2, Col.MAGENTA, 0.5 * power); 61 | primative.drawCircle(world.tilewidth / 2, world.tileheight / 2, targetradius); 62 | 63 | //Inner animations 64 | primative.lineStyle(2, Col.MAGENTA, 0.3 * power); 65 | primative.drawCircle(world.tilewidth / 2, world.tileheight / 2, targetradius * power); 66 | primative.lineStyle(2, Col.MAGENTA, 0.2 * power); 67 | primative.drawCircle(world.tilewidth / 2, world.tileheight / 2, targetradius * power * 3 / 4); 68 | primative.lineStyle(2, Col.MAGENTA, 0.1 * power); 69 | primative.drawCircle(world.tilewidth / 2, world.tileheight / 2, targetradius * power / 2); 70 | 71 | primative.visible = true; 72 | } 73 | 74 | public function updatelaser(power:Float){ 75 | primative.clear(); 76 | primative.x = x; 77 | primative.y = y; 78 | 79 | primative.moveTo(0, 0); 80 | primative.lineStyle(2, Col.RED, 0.75 * power); 81 | primative.lineTo(endx - primative.x + 5, endy - primative.y + 5 - 5); 82 | primative.lineTo(5, 5); 83 | primative.visible = true; 84 | } 85 | 86 | public function updatetowerradius(){ 87 | primative.clear(); 88 | primative.moveTo(0, 0); 89 | primative.lineStyle(3, Col.WHITE, 0.3); 90 | primative.drawCircle(world.tilewidth / 2, world.tileheight / 2, targetradius); 91 | } 92 | 93 | public function inittype(){ 94 | switch(type){ 95 | case GOAL: 96 | var tileset:Tileset = Gfx.gettileset("goal"); 97 | sprite = new h2d.Anim(tileset.tiles, 0); 98 | sprite.x = x; 99 | sprite.y = y; 100 | baseframe = (GameData.currentstage - 1) * 2; 101 | Game.towerlayer.addChild(sprite); 102 | case ENEMY: 103 | if(x < 0){ 104 | direction = Direction.RIGHT; 105 | }else if(y < 0){ 106 | direction = Direction.DOWN; 107 | }else if(world.gridx(x) > 17){ 108 | direction = Direction.LEFT; 109 | }else{ 110 | direction = Direction.UP; 111 | } 112 | 113 | maxhp = 5; 114 | hp = maxhp; 115 | 116 | var tileset:Tileset = Gfx.gettileset("enemies"); 117 | sprite = new h2d.Anim(tileset.tiles, 0); 118 | sprite.x = x; 119 | sprite.y = y; 120 | sprite.currentFrame = baseframe; 121 | Game.monsterlayer.addChild(sprite); 122 | 123 | primative = new h2d.Graphics(); 124 | primative.x = x; 125 | primative.y = y - 10; 126 | primative.moveTo(0, 0); 127 | primative.beginFill(Col.LIGHTGREEN, 1.0); 128 | primative.drawRect(0, 0, world.tilewidth * 1.5, 3); 129 | primative.endFill(); 130 | primative.visible = false; 131 | 132 | Game.monsterlayer.addChild(primative); 133 | case TOWER_SHOOTY: 134 | timetillnextshot = 0; 135 | firerate = GameData.towers.shooty.level1.firerate; 136 | timetillnextshot = 0; 137 | targetradius = GameData.towers.shooty.level1.radius; 138 | bulletdamage = GameData.towers.shooty.level1.damage; 139 | level = 1; 140 | baseframe = 0; 141 | 142 | var tileset:Tileset = Gfx.gettileset("towers"); 143 | sprite = new h2d.Anim(tileset.tiles, 0); 144 | sprite.x = x; 145 | sprite.y = y; 146 | Game.towerlayer.addChild(sprite); 147 | 148 | primative = new h2d.Graphics(); 149 | primative.x = x; 150 | primative.y = y; 151 | updatetowerradius(); 152 | primative.visible = false; 153 | 154 | //Let's try a fancy new heaps thing! 155 | var interaction = new h2d.Interactive(world.tilewidth, world.tileheight, sprite); 156 | 157 | interaction.onOver = function(event : hxd.Event) { 158 | sprite.alpha = 0.7; 159 | primative.visible = true; 160 | } 161 | 162 | interaction.onOut = function(event : hxd.Event) { 163 | sprite.alpha = 1; 164 | primative.visible = false; 165 | } 166 | 167 | Game.uilayer.addChild(primative); 168 | case TOWER_BEAM: 169 | timetillnextshot = 0; 170 | firerate = GameData.towers.beam.level1.firerate; 171 | timetillnextshot = 0; 172 | targetradius = GameData.towers.beam.level1.radius; 173 | bulletdamage = GameData.towers.beam.level1.damage; 174 | level = 1; 175 | //Pick a direction based on nearby path 176 | direction = Game.towerdirection(world.gridx(x), world.gridy(y), world); 177 | switch(direction){ 178 | case Direction.LEFT: baseframe = 18; 179 | case Direction.RIGHT: baseframe = 24; 180 | case Direction.UP: baseframe = 30; 181 | case Direction.DOWN: baseframe = 36; 182 | } 183 | 184 | var tileset:Tileset = Gfx.gettileset("towers"); 185 | sprite = new h2d.Anim(tileset.tiles, 0); 186 | sprite.x = x; 187 | sprite.y = y; 188 | Game.towerlayer.addChild(sprite); 189 | 190 | primative = new h2d.Graphics(); 191 | primative.x = x; 192 | primative.y = y; 193 | updatetowerradius(); 194 | primative.visible = false; 195 | 196 | //Let's try a fancy new heaps thing! 197 | var interaction = new h2d.Interactive(world.tilewidth, world.tileheight, sprite); 198 | 199 | interaction.onOver = function(event : hxd.Event) { 200 | sprite.alpha = 0.7; 201 | //primative.visible = true; 202 | } 203 | 204 | interaction.onOut = function(event : hxd.Event) { 205 | sprite.alpha = 1; 206 | //primative.visible = false; 207 | } 208 | 209 | Game.uilayer.addChild(primative); 210 | case TOWER_VORTEX: 211 | firerate = GameData.towers.vortex.level1.firerate; 212 | timetillnextshot = 0; 213 | targetradius = GameData.towers.vortex.level1.radius; 214 | bulletdamage = GameData.towers.vortex.level1.damage; 215 | level = 1; 216 | baseframe = 6; 217 | 218 | var tileset:Tileset = Gfx.gettileset("towers"); 219 | sprite = new h2d.Anim(tileset.tiles, 0); 220 | sprite.x = x; 221 | sprite.y = y; 222 | Game.towerlayer.addChild(sprite); 223 | 224 | primative = new h2d.Graphics(); 225 | primative.x = x; 226 | primative.y = y; 227 | updatetowerradius(); 228 | primative.visible = false; 229 | 230 | //Let's try a fancy new heaps thing! 231 | var interaction = new h2d.Interactive(world.tilewidth, world.tileheight, sprite); 232 | 233 | interaction.onOver = function(event : hxd.Event) { 234 | sprite.alpha = 0.7; 235 | primative.visible = true; 236 | } 237 | 238 | interaction.onOut = function(event : hxd.Event) { 239 | sprite.alpha = 1; 240 | primative.visible = false; 241 | } 242 | 243 | Game.uilayer.addChild(primative); 244 | case TOWER_LASER: 245 | firerate = GameData.towers.laser.level1.firerate; 246 | timetillnextshot = 0; 247 | targetradius = GameData.towers.laser.level1.radius; 248 | bulletdamage = GameData.towers.laser.level1.damage; 249 | level = 1; 250 | baseframe = 12; 251 | 252 | var tileset:Tileset = Gfx.gettileset("towers"); 253 | sprite = new h2d.Anim(tileset.tiles, 0); 254 | sprite.x = x; 255 | sprite.y = y; 256 | Game.towerlayer.addChild(sprite); 257 | 258 | primative = new h2d.Graphics(); 259 | primative.x = x; 260 | primative.y = y; 261 | updatetowerradius(); 262 | primative.visible = false; 263 | 264 | //Let's try a fancy new heaps thing! 265 | var interaction = new h2d.Interactive(world.tilewidth, world.tileheight, sprite); 266 | 267 | interaction.onOver = function(event : hxd.Event) { 268 | sprite.alpha = 0.7; 269 | primative.visible = true; 270 | } 271 | 272 | interaction.onOut = function(event : hxd.Event) { 273 | sprite.alpha = 1; 274 | primative.visible = false; 275 | } 276 | 277 | Game.uilayer.addChild(primative); 278 | case BULLET: 279 | x += centerx; 280 | y += centery; 281 | var tileset:Tileset = Gfx.gettileset("particles"); 282 | sprite = new h2d.Anim(tileset.tiles, 0); 283 | sprite.x = x; 284 | sprite.y = y; 285 | Game.bulletlayer.addChild(sprite); 286 | case BEAM: 287 | x += centerx; 288 | y += centery; 289 | //We attach the sprite elsewhere 290 | case VORTEX: 291 | x += centerx; 292 | y += centery; 293 | 294 | primative = new h2d.Graphics(); 295 | primative.x = x; 296 | primative.y = y; 297 | primative.visible = false; 298 | case LASER: 299 | x += centerx; 300 | y += centery; 301 | 302 | primative = new h2d.Graphics(); 303 | primative.x = x; 304 | primative.y = y; 305 | primative.visible = false; 306 | default: 307 | throw("Error: cannot create an entity without a type."); 308 | } 309 | } 310 | 311 | public function picknewdirection(){ 312 | var heat_up:Int = world.heatat(x + Std.int(world.tilewidth / 2), y - Std.int(world.tileheight / 2)); 313 | var heat_down:Int = world.heatat(x + Std.int(world.tilewidth / 2), y + Std.int(world.tileheight * 3 / 2)); 314 | var heat_left:Int = world.heatat(x - Std.int(world.tilewidth / 2), y + Std.int(world.tileheight / 2)); 315 | var heat_right:Int = world.heatat(x + Std.int(world.tilewidth * 3 / 2), y + Std.int(world.tileheight / 2)); 316 | 317 | //If there's only one valid direction, then we're at the end! 318 | if(heat_up + heat_down + heat_left + heat_right >= 30000){ 319 | Game.monsterreachestheend(this); 320 | return; 321 | } 322 | 323 | if(heat_up < heat_down && heat_up < heat_left && heat_up < heat_right){ 324 | direction = Direction.UP; 325 | return; 326 | } 327 | 328 | if(heat_down < heat_left && heat_down < heat_right){ 329 | direction = Direction.DOWN; 330 | return; 331 | } 332 | 333 | if(heat_left < heat_right){ 334 | direction = Direction.LEFT; 335 | return; 336 | } 337 | 338 | direction = Direction.RIGHT; 339 | } 340 | 341 | public function standardenemymove(){ 342 | switch(direction){ 343 | case Direction.UP: 344 | vx = 0; 345 | vy = -speed * Game.speed; 346 | case Direction.DOWN: 347 | vx = 0; 348 | vy = speed * Game.speed; 349 | case Direction.LEFT: 350 | vx = -speed * Game.speed; 351 | vy = 0; 352 | case Direction.RIGHT: 353 | vx = speed * Game.speed; 354 | vy = 0; 355 | } 356 | 357 | if(speedmultiplier < 1.0){ 358 | vx = vx * speedmultiplier; 359 | vy = vy * speedmultiplier; 360 | 361 | if(speeddampen > 0){ 362 | speeddampen -= Core.deltatime * Game.speed; 363 | }else{ 364 | speedmultiplier += 0.1; 365 | if(speedmultiplier > 1.0) speedmultiplier = 1.0; 366 | } 367 | } 368 | 369 | var testx:Float = x + vx; 370 | var testy:Float = y + vy; 371 | 372 | switch(direction){ 373 | case Direction.DOWN: 374 | testy += world.tileheight; 375 | case Direction.RIGHT: 376 | testx += world.tilewidth; 377 | default: 378 | } 379 | 380 | //If testx, testy is a collision, stop moving and instead change direction 381 | if(world.heatat(testx, testy) >= 10000){ 382 | var olddirection:Direction = direction; 383 | //Pick a new direction 384 | picknewdirection(); 385 | 386 | //Recenter on grid (this is a bit messy) 387 | if(direction == Direction.UP || direction == Direction.DOWN){ 388 | if(olddirection == Direction.LEFT){ 389 | x = x - world.gridxoffset(x); 390 | }else{ 391 | x = x + vx; 392 | x = x - world.gridxoffset(x); 393 | } 394 | }else{ 395 | if(olddirection == Direction.UP){ 396 | y = y - world.gridyoffset(y); 397 | }else{ 398 | y = y + vy; 399 | y = y - world.gridyoffset(y); 400 | } 401 | } 402 | 403 | //Don't move until next update 404 | vx = 0; vy = 0; 405 | } 406 | 407 | //Ok, actually move 408 | x = x + vx; 409 | y = y + vy; 410 | } 411 | 412 | public function update(){ 413 | if(destroyed) return; 414 | 415 | switch(type){ 416 | case GOAL: 417 | case ENEMY: 418 | standardenemymove(); 419 | case TOWER_SHOOTY: 420 | timetillframechange -= Core.deltatime * Game.speed; 421 | if(timetillframechange <= 0){ 422 | offsetframe = 0; 423 | } 424 | 425 | timetillnextshot -= Core.deltatime * Game.speed; 426 | if(timetillnextshot <= 0){ 427 | Game.picktarget(this); 428 | if(targetentity != null){ 429 | Game.createbullet(this, targetentity); 430 | offsetframe = 1; 431 | timetillframechange = 0.25; 432 | } 433 | timetillnextshot = firerate; 434 | } 435 | case TOWER_BEAM: 436 | timetillframechange -= Core.deltatime * Game.speed; 437 | if(timetillframechange <= 0){ 438 | offsetframe = 0; 439 | } 440 | 441 | timetillnextshot -= Core.deltatime * Game.speed; 442 | if(timetillnextshot <= 0){ 443 | Game.picktarget(this); 444 | if(targetentity != null){ 445 | Game.createbeam(this); 446 | offsetframe = 1; 447 | timetillframechange = 0.25; 448 | } 449 | timetillnextshot = firerate; 450 | } 451 | case TOWER_VORTEX: 452 | timetillframechange -= Core.deltatime * Game.speed; 453 | if(timetillframechange <= 0){ 454 | offsetframe = 0; 455 | } 456 | 457 | timetillnextshot -= Core.deltatime * Game.speed; 458 | if(timetillnextshot <= 0){ 459 | Game.picktarget(this); 460 | if(targetentity != null){ 461 | Game.createvortex(this); 462 | offsetframe = 1; 463 | timetillframechange = 0.25; 464 | } 465 | timetillnextshot = firerate; 466 | } 467 | case TOWER_LASER: 468 | timetillframechange -= Core.deltatime * Game.speed; 469 | if(timetillframechange <= 0){ 470 | offsetframe = 0; 471 | } 472 | 473 | timetillnextshot -= Core.deltatime * Game.speed; 474 | if(timetillnextshot <= 0){ 475 | Game.picktarget(this); 476 | if(targetentity != null){ 477 | Game.createlaser(this, targetentity); 478 | offsetframe = 1; 479 | timetillframechange = 0.25; 480 | } 481 | timetillnextshot = firerate; 482 | } 483 | case BULLET: 484 | //Do nothing 485 | case BEAM: 486 | //Do nothing 487 | case VORTEX: 488 | //Do nothing 489 | case LASER: 490 | //Do nothing 491 | default: 492 | throw("Error: cannot create an entity without a type."); 493 | } 494 | } 495 | 496 | public function render(){ 497 | if(destroyed) return; 498 | 499 | switch(type){ 500 | case GOAL: 501 | sprite.x = x; 502 | sprite.y = y; 503 | 504 | sprite.currentFrame = baseframe + Game.twoframe; 505 | case ENEMY: 506 | sprite.x = x; 507 | sprite.y = y - 5; 508 | sprite.currentFrame = baseframe + Game.twoframe; 509 | 510 | primative.x = x - (world.tilewidth * 0.25); 511 | primative.y = y - 10; 512 | case TOWER_SHOOTY: 513 | sprite.x = x; 514 | sprite.y = y; 515 | 516 | sprite.currentFrame = baseframe + offsetframe; 517 | case TOWER_BEAM: 518 | sprite.x = x; 519 | sprite.y = y; 520 | 521 | sprite.currentFrame = baseframe + offsetframe; 522 | case TOWER_VORTEX: 523 | sprite.x = x; 524 | sprite.y = y; 525 | 526 | sprite.currentFrame = baseframe + offsetframe; 527 | case TOWER_LASER: 528 | sprite.x = x; 529 | sprite.y = y; 530 | 531 | sprite.currentFrame = baseframe + offsetframe; 532 | case BULLET: 533 | sprite.x = x; 534 | sprite.y = y; 535 | case BEAM: 536 | //Don't mess with the beam position 537 | case VORTEX: 538 | //Don't mess with the vortex position 539 | case LASER: 540 | //Don't mess with the laser position 541 | default: 542 | throw("Error: cannot create an entity without a type."); 543 | } 544 | } 545 | 546 | public function slowenemy(_speedmult:Float, _speeddamp:Float){ 547 | if(speedmultiplier > _speedmult){ 548 | speedmultiplier = _speedmult; 549 | } 550 | 551 | speeddampen = _speeddamp; 552 | } 553 | 554 | public function damageenemy(dmg:Float){ 555 | if(destroyed) return; 556 | 557 | hp -= dmg; 558 | 559 | if(hp <= 0){ 560 | Game.gold += Waves.reward; 561 | Game.playsound("destroy"); 562 | destroy(); 563 | }else{ 564 | primative.clear(); 565 | primative.moveTo(0, 0); 566 | primative.beginFill(Col.LIGHTGREEN, 1.0); 567 | primative.drawRect(0, 0, ((world.tilewidth * 1.5) * hp) / maxhp, 3); 568 | primative.endFill(); 569 | 570 | primative.visible = true; 571 | } 572 | } 573 | 574 | /* Mark entity for later removal */ 575 | public function destroy(){ 576 | if(sprite != null){ 577 | sprite.remove(); 578 | } 579 | 580 | if(primative != null){ 581 | primative.remove(); 582 | } 583 | 584 | sprite = null; 585 | primative = null; 586 | destroyed = true; 587 | } 588 | 589 | public function shrinkdestroy(){ 590 | Actuate.tween(sprite, 0.1, {scaleX: 0, scaleY:0 }) 591 | .onComplete(destroy); 592 | } 593 | 594 | public var x:Float; 595 | public var y:Float; 596 | public var centerx:Float; 597 | public var centery:Float; 598 | public var type:EntityType; 599 | public var world:World; 600 | public var destroyed:Bool; 601 | 602 | public var vx:Float; 603 | public var vy:Float; 604 | public var endx:Float; 605 | public var endy:Float; 606 | public var speed:Float; 607 | public var direction:Direction; 608 | public var speedmultiplier:Float; 609 | public var speeddampen:Float; 610 | 611 | public var maxhp:Float; 612 | public var hp:Float; 613 | 614 | public var sprite:h2d.Anim; 615 | public var primative:h2d.Graphics; 616 | 617 | public var targetradius:Float; 618 | public var targetentity:Entity; 619 | public var firerate:Float; 620 | public var bulletdamage:Float; 621 | public var timetillnextshot:Float; 622 | public var level:Int; 623 | public var timetillframechange:Float; 624 | 625 | //For animation 626 | public var animpercent:Float; 627 | public var baseframe:Int; 628 | public var offsetframe:Int; 629 | } -------------------------------------------------------------------------------- /data/pixelzim.fnt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | --------------------------------------------------------------------------------