diff --git a/WORK.md b/WORK.md index 13fdd28..8bdeeb8 100644 --- a/WORK.md +++ b/WORK.md @@ -15,3 +15,4 @@ * game panel rework * pause * game state: config, map, entities, players +* game menu pack progress diff --git a/gulpfile.js b/gulpfile.js index 9b94d41..82f603d 100755 --- a/gulpfile.js +++ b/gulpfile.js @@ -139,7 +139,7 @@ const server = new Project( name: 'server', sources: ['src/server/haxe'], main: 'ru.m.tankz.server.Server', - resources: [ + assets: [ 'src/common/resources/level', ] }), diff --git a/src/client/haxe/ru/m/tankz/Init.hx b/src/client/haxe/ru/m/tankz/Init.hx index 5df4f35..79df69a 100644 --- a/src/client/haxe/ru/m/tankz/Init.hx +++ b/src/client/haxe/ru/m/tankz/Init.hx @@ -66,7 +66,7 @@ class Init { public static function init():Void { theme = new AppTheme(); resources = new Resources(); - levelBundle = new CachedLevelBundle(new ClientLevelSource()/*, new SharedObjectStorage()*/); //ToDo: update + levelBundle = new CachedLevelBundle(new ClientLevelSource(), new SharedObjectStorage()); levelBundle.load(); configBundle = new ConfigBundle(); settingsStorage = new SettingsStorage(); diff --git a/src/client/haxe/ru/m/tankz/bundle/ClientLevelSource.hx b/src/client/haxe/ru/m/tankz/bundle/ClientLevelSource.hx index df976e3..858e801 100644 --- a/src/client/haxe/ru/m/tankz/bundle/ClientLevelSource.hx +++ b/src/client/haxe/ru/m/tankz/bundle/ClientLevelSource.hx @@ -11,15 +11,14 @@ class ClientLevelSource implements ILevelSource { public function new() {} + public function resolveMeta(id:PackId):LevelPackMeta { + var bytes = Assets.getBytes('level/${id}.zip'); + return LevelUtil.getMeta(bytes); + } + public function resolve(id:PackId):LevelPack { var bytes = Assets.getBytes('level/${id}.zip'); - return { - id: id, - data: LevelUtil.unpack(bytes).map(function(level) { - level.packId = id; - return level; - }), - }; + return LevelUtil.unpack(bytes); } public function list():Array { diff --git a/src/client/haxe/ru/m/tankz/view/common/NetworkStateView.hx b/src/client/haxe/ru/m/tankz/view/common/NetworkStateView.hx index b24627d..7e635f5 100644 --- a/src/client/haxe/ru/m/tankz/view/common/NetworkStateView.hx +++ b/src/client/haxe/ru/m/tankz/view/common/NetworkStateView.hx @@ -27,24 +27,25 @@ import ru.m.tankz.view.popup.LoginPopup; userLabel.text = ""; loginButton.visible = false; logoutButton.visible = false; + var skin:SpriteSkin = cast stateView.skin; switch state { case OFFLINE: - cast(stateView.skin, SpriteSkin).background.color = "black"; + skin.background.color = "black"; stateView.visible = false; visible = false; case CONNECT: - cast(stateView.skin, SpriteSkin).background.color = "yellow"; + skin.background.color = "yellow"; case CONNECTED: - cast(stateView.skin, SpriteSkin).background.color = "gray"; + skin.background.color = "gray"; loginButton.visible = true; case LOGIN: - cast(stateView.skin, SpriteSkin).background.color = "yellow"; + skin.background.color = "yellow"; case ONLINE(user): - cast(stateView.skin, SpriteSkin).background.color = "green"; + skin.background.color = "green"; userLabel.text = user.name; logoutButton.visible = true; case ERROR(error): - cast(stateView.skin, SpriteSkin).background.color = "red"; + skin.background.color = "red"; userLabel.text = Std.string(error).substr(0, 10); } stateView.toRedraw(); diff --git a/src/common/haxe/ru/m/tankz/bundle/CachedLevelBundle.hx b/src/common/haxe/ru/m/tankz/bundle/CachedLevelBundle.hx index da72ebc..d470b26 100644 --- a/src/common/haxe/ru/m/tankz/bundle/CachedLevelBundle.hx +++ b/src/common/haxe/ru/m/tankz/bundle/CachedLevelBundle.hx @@ -26,18 +26,21 @@ class CachedLevelBundle implements ILevelBundle { public function get(id:PackId):LevelPack { if (cache.exists(id)) { return cache.get(id); - } else if (storage != null && storage.exists(id)) { - var result = storage.read(id); - cache.set(id, result); - return result; - } else { - var result = source.resolve(id); - if (storage != null) { - storage.write(id, result); - } - cache.set(id, result); - return result; } + if (storage != null && storage.exists(id)) { + var result:LevelPack = storage.read(id); + var meta = source.resolveMeta(id); + if (result.meta != null && result.meta.date.getTime() == meta.date.getTime()) { + cache.set(id, result); + return result; + } + } + var result = source.resolve(id); + if (storage != null) { + storage.write(id, result); + } + cache.set(id, result); + return result; } public function list():Array { diff --git a/src/common/haxe/ru/m/tankz/bundle/ILevelBundle.hx b/src/common/haxe/ru/m/tankz/bundle/ILevelBundle.hx index 1d6a4da..1533fb8 100644 --- a/src/common/haxe/ru/m/tankz/bundle/ILevelBundle.hx +++ b/src/common/haxe/ru/m/tankz/bundle/ILevelBundle.hx @@ -5,6 +5,7 @@ import ru.m.tankz.Type; interface ILevelSource { public function resolve(id:PackId):LevelPack; + public function resolveMeta(id:PackId):LevelPackMeta; public function list():Array; } diff --git a/src/common/haxe/ru/m/tankz/config/Config.hx b/src/common/haxe/ru/m/tankz/config/Config.hx index e697fcd..21e6160 100644 --- a/src/common/haxe/ru/m/tankz/config/Config.hx +++ b/src/common/haxe/ru/m/tankz/config/Config.hx @@ -123,19 +123,26 @@ typedef LevelConfig = { @:optional var size:{width:Int, height:Int}; } +typedef LevelPackMeta = { + var id:PackId; + var author:String; + var date:Date; +} + typedef LevelPack = { var id:PackId; + var meta:LevelPackMeta; var data:Array; } typedef ConfigSource = { var game:GameConfig; - var map: MapConfig; - var bricks: Array; - var presets: Array; - var points: Array; - var tanks: Array; - var bonuses: Array; + var map:MapConfig; + var bricks:Array; + var presets:Array; + var points:Array; + var tanks:Array; + var bonuses:Array; } class Config { diff --git a/src/common/haxe/ru/m/tankz/game/PackProgress.hx b/src/common/haxe/ru/m/tankz/game/PackProgress.hx index 91929ad..5484446 100644 --- a/src/common/haxe/ru/m/tankz/game/PackProgress.hx +++ b/src/common/haxe/ru/m/tankz/game/PackProgress.hx @@ -43,4 +43,8 @@ class PackProgress { } completed[levelId].presets[presetId] = result; } + + public function toString():String { + return 'PackProgress(id=${id}, completed=${completed}})'; + } } diff --git a/src/common/haxe/ru/m/tankz/util/LevelUtil.hx b/src/common/haxe/ru/m/tankz/util/LevelUtil.hx index c8ed405..5bcb5a9 100644 --- a/src/common/haxe/ru/m/tankz/util/LevelUtil.hx +++ b/src/common/haxe/ru/m/tankz/util/LevelUtil.hx @@ -4,7 +4,6 @@ import haxe.io.Bytes; import haxe.io.BytesInput; import haxe.io.BytesOutput; import haxe.zip.Entry; -import haxe.zip.Tools; import haxe.zip.Writer; import ru.m.tankz.config.Config; import ru.m.tankz.Type; @@ -37,7 +36,7 @@ class LevelUtil { } } - public static function dumps(config:Config, level:LevelConfig):String { + public static function dumps(level:LevelConfig):String { var bricksStr = level.data.join(''); return Yaml.render({ data: bricksStr, @@ -54,48 +53,47 @@ class LevelUtil { } } - private static function extract(entry:Entry):LevelConfig { - var bytes:Bytes = entry.data; - if (entry.compressed) { - #if ((flash || html5) && lime) - bytes = cast(bytes, lime.utils.Bytes).decompress(lime.utils.CompressionAlgorithm.DEFLATE); - #else - bytes = haxe.zip.Reader.unzip(entry); - #end - } - var level = LevelUtil.loads(bytes.toString()); - if (level.id == null) { - level.id = Std.parseInt(entry.fileName.split("level").pop()); - } - return level; - } - - private static function compress(level:LevelConfig):Entry { - var content = LevelUtil.dumps(null, level); - var bytes = Bytes.ofString(content); - var crc = haxe.crypto.Crc32.make(bytes); - var result:Entry = { - fileName: '${formatLevel(level.id)}.yml', - fileSize: bytes.length, - fileTime: Date.now(), - compressed: false, - dataSize: bytes.length, - data: bytes, - crc32: crc, - }; - Tools.compress(result, 9); - return result; - } - - public static function unpack(bytes:Bytes):Array { + public static function getMeta(bytes:Bytes):LevelPackMeta { var files = haxe.zip.Reader.readZip(new BytesInput(bytes)); - return Lambda.array(files.map(extract)); + for (entry in files) { + if (entry.fileName == "meta.yml") { + var content = ZipUtil.extractEntry(entry); + return Yaml.parse(content, Parser.options().useObjects()); + } + } + return null; } - public static function pack(data:Array):Bytes { + public static function unpack(bytes:Bytes):LevelPack { + var files = haxe.zip.Reader.readZip(new BytesInput(bytes)); + var meta = null; + var data:Array = []; + for (entry in files) { + var content = ZipUtil.extractEntry(entry); + if (entry.fileName == "meta.yml") { + meta = Yaml.parse(content, Parser.options().useObjects()); + } else { + var level:LevelConfig = LevelUtil.loads(content); + level.packId = meta.id; + if (level.id == null) { + level.id = Std.parseInt(entry.fileName.split("level").pop()); + } + data.push(level); + } + } + return { + id: meta.id, + meta: meta, + data: data, + } + } + + public static function pack(pack:LevelPack):Bytes { var output = new BytesOutput(); var writer = new Writer(output); - writer.write(Lambda.list(data.map(compress))); + var metaEntry:Entry = ZipUtil.createEntry("meta.yml", Yaml.render(pack.meta, Renderer.options().setFlowLevel(1))); + var levelEntries:Array = pack.data.map(function(level) return ZipUtil.createEntry('${formatLevel(level.id)}.yml', dumps(level))); + writer.write(Lambda.list([metaEntry].concat(levelEntries))); return output.getBytes(); } } diff --git a/src/common/haxe/ru/m/tankz/util/ZipUtil.hx b/src/common/haxe/ru/m/tankz/util/ZipUtil.hx new file mode 100644 index 0000000..abaa6b9 --- /dev/null +++ b/src/common/haxe/ru/m/tankz/util/ZipUtil.hx @@ -0,0 +1,36 @@ +package ru.m.tankz.util; + +import haxe.io.Bytes; +import haxe.zip.Entry; +import haxe.zip.Tools; + +class ZipUtil { + + public static function extractEntry(entry:Entry):String { + var bytes:Bytes = entry.data; + if (entry.compressed) { + #if ((flash || html5) && lime) + bytes = cast(bytes, lime.utils.Bytes).decompress(lime.utils.CompressionAlgorithm.DEFLATE); + #else + bytes = haxe.zip.Reader.unzip(entry); + #end + } + return bytes.toString(); + } + + public static function createEntry(name:String, content:String):Entry { + var bytes = Bytes.ofString(content); + var crc = haxe.crypto.Crc32.make(bytes); + var result:Entry = { + fileName: name, + fileSize: bytes.length, + fileTime: Date.now(), + compressed: false, + dataSize: bytes.length, + data: bytes, + crc32: crc, + }; + Tools.compress(result, 9); + return result; + } +} diff --git a/src/common/resources/level/classic_modern.zip b/src/common/resources/level/classic_modern.zip index c8ea34a..fca9f58 100644 Binary files a/src/common/resources/level/classic_modern.zip and b/src/common/resources/level/classic_modern.zip differ diff --git a/src/common/resources/level/classic_standard.zip b/src/common/resources/level/classic_standard.zip index bba7a99..f33778e 100644 Binary files a/src/common/resources/level/classic_standard.zip and b/src/common/resources/level/classic_standard.zip differ diff --git a/src/common/resources/level/death_standard.zip b/src/common/resources/level/death_standard.zip index 86ae2b4..6d7287a 100644 Binary files a/src/common/resources/level/death_standard.zip and b/src/common/resources/level/death_standard.zip differ diff --git a/src/common/resources/level/dota_standard.zip b/src/common/resources/level/dota_standard.zip index 8685991..9e4165e 100644 Binary files a/src/common/resources/level/dota_standard.zip and b/src/common/resources/level/dota_standard.zip differ diff --git a/src/editor/haxe/ru/m/tankz/editor/EditorStorage.hx b/src/editor/haxe/ru/m/tankz/editor/EditorStorage.hx index 234cf00..44ee2b1 100644 --- a/src/editor/haxe/ru/m/tankz/editor/EditorStorage.hx +++ b/src/editor/haxe/ru/m/tankz/editor/EditorStorage.hx @@ -2,7 +2,7 @@ package ru.m.tankz.editor; import haxe.DynamicAccess; import haxework.storage.SharedObjectStorage; -import ru.m.tankz.Type; +import ru.m.tankz.config.Config; class EditorStorage extends SharedObjectStorage { private static inline var VERSION = 2; @@ -11,11 +11,11 @@ class EditorStorage extends SharedObjectStorage { super('editor_${VERSION}'); } - public function packList():Array { + public function packList():Array { var result = []; var data:DynamicAccess = so.data; for (id in data.keys()) { - result.push(PackId.fromString(id)); + result.push(read(id)); } return result; } diff --git a/src/editor/haxe/ru/m/tankz/editor/view/PackFrame.hx b/src/editor/haxe/ru/m/tankz/editor/view/PackFrame.hx index 06c4d41..99952da 100644 --- a/src/editor/haxe/ru/m/tankz/editor/view/PackFrame.hx +++ b/src/editor/haxe/ru/m/tankz/editor/view/PackFrame.hx @@ -118,6 +118,7 @@ using ru.m.tankz.view.ViewUtil; pack.data[data.id] = data; levels.data = pack.data; level = data; + pack.meta.date = Date.now(); storage.write(pack.id, pack); } diff --git a/src/editor/haxe/ru/m/tankz/editor/view/PackListFrame.hx b/src/editor/haxe/ru/m/tankz/editor/view/PackListFrame.hx index 70cae0a..978af77 100644 --- a/src/editor/haxe/ru/m/tankz/editor/view/PackListFrame.hx +++ b/src/editor/haxe/ru/m/tankz/editor/view/PackListFrame.hx @@ -14,7 +14,7 @@ import ru.m.tankz.util.LevelUtil; @:template class PackListFrame extends FrameView { public static inline var ID = "pack_list"; - @:view("packs") var packView:ActionDataView; + @:view("packs") var packView:ActionDataView; @:provide static var levelBundle:ILevelBundle; @:provide static var switcher:FrameSwitcher; @@ -29,18 +29,19 @@ import ru.m.tankz.util.LevelUtil; packView.data = storage.packList(); } - private function onPackAction(value:PackId, action:PackAction):Void { + private function onPackAction(value:LevelPack, action:PackAction):Void { switch action { case EXPORT: - var pack:LevelPack = storage.read(value); FileUtil.save({ - name: '${value}.zip', - content: LevelUtil.pack(pack.data), + name: '${value.id}.zip', + content: LevelUtil.pack(value), }); + storage.write(value.id, value); + packView.data = storage.packList(); case EDIT: - switcher.change(PackFrame.ID, storage.read(value)); + switcher.change(PackFrame.ID, value); case DELETE: - storage.delete(value); + storage.delete(value.id); packView.data = storage.packList(); } } @@ -48,30 +49,22 @@ import ru.m.tankz.util.LevelUtil; private function create():Void { PackPopup.instance.show().then(function(packId:PackId) { if (packId != null) { - var levelPack:LevelPack = { + var pack:LevelPack = { id: packId, + meta: {id: packId, author: "default", date: null}, data: [], } - storage.write(levelPack.id, levelPack); - packView.data.push(levelPack.id); - packView.data = packView.data; + storage.write(pack.id, pack); + packView.data = storage.packList(); } }); } private function open():Void { FileUtil.browse().then(function(file:FileContent) { - var packId = PackId.fromArray(file.name.split("/").pop().split(".").shift().split("_")); - var levelPack:LevelPack = { - id: packId, - data: LevelUtil.unpack(file.content).map(function(level) { - level.packId = packId; - return level; - }), - }; - storage.write(levelPack.id, levelPack); - packView.data.push(levelPack.id); - packView.data = packView.data; + var pack:LevelPack = LevelUtil.unpack(file.content); + storage.write(pack.id, pack); + packView.data = storage.packList(); }); } } diff --git a/src/editor/haxe/ru/m/tankz/editor/view/PackView.hx b/src/editor/haxe/ru/m/tankz/editor/view/PackView.hx index bb2591a..2b37834 100644 --- a/src/editor/haxe/ru/m/tankz/editor/view/PackView.hx +++ b/src/editor/haxe/ru/m/tankz/editor/view/PackView.hx @@ -5,7 +5,7 @@ import haxework.view.data.DataView; import haxework.view.form.ButtonView; import haxework.view.group.HGroupView; import haxework.view.list.ListView; -import ru.m.tankz.Type; +import ru.m.tankz.config.Config; using ru.m.tankz.view.ViewUtil; @@ -15,23 +15,23 @@ enum PackAction { DELETE; } -@:template class PackView extends HGroupView implements IListItemView { +@:template class PackView extends HGroupView implements IListItemView { public var item_index(default, default):Int; - public var data(default, set):PackId; + public var data(default, set):LevelPack; - private var actionSignal(get, null):Signal2; + private var actionSignal(get, null):Signal2; @:view var label:ButtonView; - private function set_data(value:PackId):PackId { + private function set_data(value:LevelPack):LevelPack { data = value; - label.text = data.toPackLabel(); + label.text = data.id.toPackLabel() + " | " + data.meta.date; return data; } - private function get_actionSignal():Signal2 { - var dataView:ActionDataView = cast parent; + private function get_actionSignal():Signal2 { + var dataView:ActionDataView = cast parent; return dataView.onDataAction; } @@ -47,7 +47,7 @@ enum PackAction { actionSignal.emit(data, DELETE); } - public static function factory(index:Int, value:PackId):PackView { + public static function factory(index:Int, value:LevelPack):PackView { var result = new PackView(); result.item_index = index; result.data = value; diff --git a/src/server/haxe/ru/m/tankz/server/bundle/ServerLevelSource.hx b/src/server/haxe/ru/m/tankz/server/bundle/ServerLevelSource.hx index 5b48ce5..a6b3358 100644 --- a/src/server/haxe/ru/m/tankz/server/bundle/ServerLevelSource.hx +++ b/src/server/haxe/ru/m/tankz/server/bundle/ServerLevelSource.hx @@ -9,20 +9,20 @@ import sys.io.File; class ServerLevelSource implements ILevelSource { - private static inline var PATH = "./resources/level"; + private static inline var PATH = "./level"; public function new() {} + public function resolveMeta(id:PackId):LevelPackMeta { + var path = FileSystem.absolutePath('${PATH}/${id}.zip'); + var bytes = File.getBytes(path); + return LevelUtil.getMeta(bytes); + } + public function resolve(id:PackId):LevelPack { var path = FileSystem.absolutePath('${PATH}/${id}.zip'); var bytes = File.getBytes(path); - return { - id: id, - data: LevelUtil.unpack(bytes).map(function(level) { - level.packId = id; - return level; - }), - }; + return LevelUtil.unpack(bytes); } public function list():Array {