[editor] support level pack edit

This commit is contained in:
2019-09-09 17:56:08 +03:00
parent 4fa5cf84fa
commit 77ddd78d84
25 changed files with 1125 additions and 467 deletions

View File

@@ -1,11 +1,8 @@
const gulp = require('gulp');
const zip = require('gulp-zip');
const foreach = require('gulp-foreach');
const gulpClean = require('gulp-clean');
const Config = require('./config.json');
const packageInfo = require('./package.json');
const {System, Sdk, Haxe, Project} = require('gulp-haxetool');
const path = require('path');
const dateformat = require('dateformat');
const argv = require('yargs').argv;
const publish = require('./tasks/gulp-publish');
@@ -26,19 +23,6 @@ exports.generate = function generate() {
});
};
exports.levels = function levels() {
return gulp.src("./src/common/level/*").pipe(foreach(function (stream, file) {
const type = file.path.substr(file.path.lastIndexOf(path.sep) + 1);
gulp.src("./src/common/level/" + type + "/*").pipe(foreach(function (stream, file) {
const name = file.path.substr(file.path.lastIndexOf(path.sep) + 1);
gulp.src("./src/common/level/" + type + "/" + name + "/*")
.pipe(zip(`${type}_${name}.zip`))
.pipe(gulp.dest("./target/levels"));
}));
return stream;
}));
};
/**
* base config
*/
@@ -59,7 +43,6 @@ const config = new Project.Config({
],
assets: [
'src/common/resources',
'target/levels'
],
flags: [
//'proto_debug',
@@ -168,7 +151,6 @@ module.exports.publish = publish(packageInfo.name, packageInfo.version, Config.P
*/
const defaultSeries = [
exports.clean,
exports.levels,
exports.generate,
module.exports['client:flash:build'],
module.exports['client:flash:html'],

1063
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,7 @@
"gulp": "^4.0.0",
"gulp-add": "0.0.2",
"gulp-clean": "^0.4.0",
"gulp-foreach": "^0.1.0",
"gulp-haxetool": "0.1.0",
"gulp-zip": "^5.0.0",
"yargs": "^13.2.4"
},
"haxeDependencies": {

View File

@@ -1,6 +1,7 @@
package ru.m.tankz.bundle;
import openfl.Assets;
import openfl.utils.AssetType;
import ru.m.tankz.config.Config;
import ru.m.tankz.Type;
import ru.m.tankz.util.LevelUtil;
@@ -12,7 +13,7 @@ class LevelBundle implements ILevelBundle {
public function new() {}
private function resolve(id:PackId):LevelPack {
var bytes = Assets.getBytes('levels/${id}.zip');
var bytes = Assets.getBytes('resources/level/${id}.zip');
return {
id: id,
data: LevelUtil.unpack(bytes).map(function(level) {
@@ -28,4 +29,14 @@ class LevelBundle implements ILevelBundle {
}
return _cache.get(id);
}
public function list():Array<PackId> {
var result = [];
for (path in Assets.list(AssetType.BINARY)) {
if (StringTools.startsWith(path, "resources/level")) {
result.push(PackId.fromString(path.split("/").pop().split(".").shift()));
}
}
return result;
}
}

View File

@@ -33,7 +33,7 @@ class LocalGame extends GameRunner {
}
}
if (humansTeams.length == 1 && result.winner == humansTeams[0]) {
var progress = gameStorage.get(new PackId(result.state.type));
var progress = gameStorage.get(result.level.packId);
progress.completeLevel(result.level.id, result.state.presetId);
gameStorage.set(progress);
}

View File

@@ -12,6 +12,8 @@ import ru.m.tankz.game.GameState;
import ru.m.tankz.storage.GameStorage;
import ru.m.tankz.Type.PackId;
using ru.m.tankz.view.ViewUtil;
@:template class LevelFrame extends FrameView<PackId> {
public static inline var ID = "level";
@@ -35,7 +37,7 @@ import ru.m.tankz.Type.PackId;
}
override public function onShow(data:PackId):Void {
header.text = data.type;
header.text = data.toPackLabel();
pack = levelBundle.get(data);
levels.data = pack.data;
}

View File

@@ -1,9 +1,11 @@
package ru.m.tankz.view;
import haxework.view.data.DataView;
import haxework.view.form.ButtonView;
import haxework.view.form.LabelView;
import haxework.view.frame.FrameSwitcher;
import haxework.view.frame.FrameView;
import ru.m.tankz.bundle.ILevelBundle;
import ru.m.tankz.game.GameInit;
import ru.m.tankz.game.GameState;
import ru.m.tankz.network.NetworkManager;
@@ -12,15 +14,19 @@ import ru.m.tankz.view.network.RoomFrame;
import ru.m.tankz.view.network.RoomListFrame;
import ru.m.tankz.view.popup.LoginPopup;
using ru.m.tankz.view.ViewUtil;
@:template class MenuFrame extends FrameView<Dynamic> {
public static var ID(default, never):String = "menu";
@:provide var gameInit:GameInit;
@:provide var switcher:FrameSwitcher;
@:provide var network:NetworkManager;
@:provide var appUpdater:Updater;
@:provide static var gameInit:GameInit;
@:provide static var switcher:FrameSwitcher;
@:provide static var network:NetworkManager;
@:provide static var appUpdater:Updater;
@:provide static var levelBundle:ILevelBundle;
@:view var packs:DataView<PackId, ButtonView>;
@:view var username:LabelView;
@:view("login") var loginButton:ButtonView;
@:view("logout") var logoutButton:ButtonView;
@@ -40,6 +46,9 @@ import ru.m.tankz.view.popup.LoginPopup;
updateButton.text = 'Update ${appUpdater.bundle.version}';
}
});
var list = levelBundle.list();
list.sort(function(a:PackId, b:PackId) return a.toPackLabel() > b.toPackLabel() ? 1 : -1);
packs.data = list;
}
override public function onHide():Void {
@@ -47,9 +56,16 @@ import ru.m.tankz.view.popup.LoginPopup;
network.stateSignal.disconnect(onConnectionState);
}
private function startGame(type:GameType):Void {
gameInit = LOCAL({state: new GameState(type), level: null});
switcher.change(LevelFrame.ID, new PackId(type));
private function packButtonFactory(index:Int, packId:PackId):ButtonView {
var result = new ButtonView();
result.style = "button.menu";
result.text = packId.toPackLabel();
return result;
}
private function startGame(packId:PackId):Void {
gameInit = LOCAL({state: new GameState(packId.type), level: null});
switcher.change(LevelFrame.ID, packId);
}
private function startNetwork():Void {

View File

@@ -1,79 +1,74 @@
---
views:
- $type: haxework.view.group.VGroupView
style: container
overflow.y: scroll
views:
- $type: haxework.view.group.VGroupView
layout.margin: 10
layout.hAlign: center
views:
- $type: haxework.view.form.LabelView
text: Tank'z
style: font
font.size: 100
geometry.margin.bottom: 30
- $type: haxework.view.form.ButtonView
style: button.menu
+onPress: ~startGame('classic')
text: Classic
- $type: haxework.view.form.ButtonView
style: button.menu
+onPress: ~startGame('dota')
text: DotA
- $type: haxework.view.form.ButtonView
style: button.menu
+onPress: ~startGame('death')
text: DeathMatch
- $type: haxework.view.SpriteView
style: line
geometry.width: 250
geometry.margin: [0, 10]
visible: false
- $type: haxework.view.form.ButtonView
style: button.menu
+onPress: ~switcher.change('record')
text: Records
visible: false
- id: network
$type: haxework.view.form.ButtonView
style: button.menu
+onPress: ~startNetwork()
text: Network
visible: false
- $type: haxework.view.form.LabelView
geometry.hAlign: right
geometry.vAlign: top
geometry.position: absolute
geometry.margin: [0, 20, 20, 0]
style: text.box
text: $r:text:version
- $type: haxework.view.group.HGroupView
style: panel
layout.margin: 10
views:
- id: settings
$type: haxework.view.form.ButtonView
style: button.settings
+onPress: ~switcher.change('settings')
- $type: haxework.view.group.VGroupView
style: container
overflow.y: scroll
views:
- $type: haxework.view.group.VGroupView
layout.margin: 10
layout.hAlign: center
views:
- $type: haxework.view.form.LabelView
text: Tank'z
style: font
font.size: 100
geometry.margin.bottom: 30
- id: packs
$type: haxework.view.data.DataView
layout:
$type: haxework.view.layout.VerticalLayout
margin: 10
factory: ~packButtonFactory
+onDataSelect: ~startGame
- $type: haxework.view.SpriteView
geometry.width: 50%
- id: username
$type: haxework.view.form.LabelView
style: text
- id: login
$type: haxework.view.form.ButtonView
style: button.login
+onPress: ~login()
- id: logout
$type: haxework.view.form.ButtonView
style: button.logout
+onPress: ~logout()
style: line
geometry.width: 250
geometry.margin: [0, 10]
visible: false
- $type: haxework.view.SpriteView
geometry.width: 50%
- id: update
$type: haxework.view.form.ButtonView
style: button.active
+onPress: ~appUpdate()
- $type: haxework.view.form.ButtonView
style: button.menu
+onPress: ~switcher.change('record')
text: Records
visible: false
- id: network
$type: haxework.view.form.ButtonView
style: button.menu
+onPress: ~startNetwork()
text: Network
visible: false
- $type: haxework.view.form.LabelView
geometry.hAlign: right
geometry.vAlign: top
geometry.position: absolute
geometry.margin: [0, 20, 20, 0]
style: text.box
text: $r:text:version
- $type: haxework.view.group.HGroupView
style: panel
layout.margin: 10
views:
- id: settings
$type: haxework.view.form.ButtonView
style: button.settings
+onPress: ~switcher.change('settings')
- $type: haxework.view.SpriteView
geometry.width: 50%
- id: username
$type: haxework.view.form.LabelView
style: text
- id: login
$type: haxework.view.form.ButtonView
style: button.login
+onPress: ~login()
- id: logout
$type: haxework.view.form.ButtonView
style: button.logout
+onPress: ~logout()
visible: false
- $type: haxework.view.SpriteView
geometry.width: 50%
- id: update
$type: haxework.view.form.ButtonView
style: button.active
+onPress: ~appUpdate()
visible: false

View File

@@ -1,5 +1,6 @@
package ru.m.tankz.view;
import ru.m.tankz.Type.PackId;
import openfl.Assets;
import ru.m.control.DeviceAction;
import ru.m.tankz.config.Config;
@@ -40,6 +41,10 @@ class ViewUtil {
return result.join(oneline ? " " : "\n");
}
public static function toPackLabel(packId:PackId):String {
return packId.name != PackId.DEFAULT ? '${packId.type} #${packId.name}' : packId.type;
}
public static function toActionLabel(action:TankAction):String {
return switch (action) {
case MOVE(d): 'MOVE_$d';

View File

@@ -80,6 +80,10 @@ abstract PackId(Array<Dynamic>) {
return new PackId(value[0], value[1]);
}
@:from static public function fromString(value:String):PackId {
return fromArray(value.split("_"));
}
@:to public inline function toString():String {
return '${type}_${name}';
}

View File

@@ -5,4 +5,5 @@ import ru.m.tankz.Type;
interface ILevelBundle {
public function get(id:PackId):LevelPack;
public function list():Array<PackId>;
}

View File

@@ -1,5 +1,9 @@
package ru.m.tankz.util;
import haxe.zip.Tools;
import haxe.io.BytesOutput;
import haxe.zip.Writer;
import flash.utils.ByteArray;
import haxe.io.Bytes;
import haxe.io.BytesInput;
import haxe.zip.Entry;
@@ -87,8 +91,32 @@ class LevelUtil {
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)}.txt',
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<LevelConfig> {
var files = Reader.readZip(new BytesInput(bytes));
return Lambda.array(files.map(extract));
}
public static function pack(data:Array<LevelConfig>):Bytes {
var output = new BytesOutput();
var writer = new Writer(output);
writer.write(Lambda.list(data.map(compress)));
return output.getBytes();
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -19,6 +19,7 @@ class Editor {
@:provide static var resources:IResources;
@:provide static var theme:ITheme;
@:provide static var switcher:FrameSwitcher;
@:provide static var storage:EditorStorage;
public static function main() {
L.push(new haxework.log.TraceLogger());
@@ -39,6 +40,7 @@ class Editor {
Provider.setFactory(IConfigBundle, ConfigBundle);
Provider.setFactory(ILevelBundle, LevelBundle);
storage = new EditorStorage();
var view = new EditorView();
Root.bind(view);

View File

@@ -0,0 +1,22 @@
package ru.m.tankz.editor;
import haxe.DynamicAccess;
import haxework.storage.SharedObjectStorage;
import ru.m.tankz.Type;
class EditorStorage extends SharedObjectStorage {
private static inline var VERSION = 2;
public function new() {
super('editor_${VERSION}');
}
public function packList():Array<PackId> {
var result = [];
var data:DynamicAccess<String> = so.data;
for (id in data.keys()) {
result.push(PackId.fromString(id));
}
return result;
}
}

View File

@@ -1,19 +1,18 @@
package ru.m.tankz.editor;
import promhx.Deferred;
import flash.events.ProgressEvent;
import flash.events.IOErrorEvent;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.net.FileReference;
import haxe.io.Bytes;
import promhx.Deferred;
import promhx.Promise;
typedef FileContent = {
var name:String;
var content:String;
var content:Bytes;
}
class FileUtil {
public static function browse():Promise<FileContent> {
@@ -30,10 +29,9 @@ class FileUtil {
});
file.addEventListener(Event.COMPLETE, function(event:Event) {
var f:FileReference = cast event.target;
var data = f.data.readUTFBytes(f.data.length);
d.resolve({
name: f.name,
content: data
content: Bytes.ofData(f.data),
});
});
file.browse();
@@ -42,7 +40,6 @@ class FileUtil {
public static function save(content:FileContent):Void {
var file = new FileReference();
file.save(content.content, content.name);
file.save(content.content.getData(), content.name);
}
}

View File

@@ -1,19 +1,18 @@
package ru.m.tankz.editor.view;
import haxework.view.list.LabelListItem;
import ru.m.tankz.util.LevelUtil;
import haxework.view.data.DataView;
import haxework.view.form.InputView;
import haxework.view.frame.FrameSwitcher;
import haxework.view.frame.FrameView;
import haxework.view.list.LabelListItem;
import haxework.view.list.ListView;
import ru.m.tankz.bundle.IConfigBundle;
import ru.m.tankz.config.Config;
import ru.m.tankz.editor.FileUtil;
import ru.m.tankz.editor.view.map.BrickView;
import ru.m.tankz.editor.view.map.MapEditView;
import ru.m.tankz.editor.view.map.SpawnPointView;
import ru.m.tankz.Type;
import ru.m.tankz.util.LevelUtil;
using ru.m.tankz.view.ViewUtil;
@@ -24,6 +23,7 @@ using ru.m.tankz.view.ViewUtil;
public var level(default, set):LevelConfig;
private var fileName:String;
private var pack:LevelPack;
@:view var levels:ListView<LevelConfig>;
@:view var levelName:InputView;
@@ -36,6 +36,7 @@ using ru.m.tankz.view.ViewUtil;
@:provide var configBundle:IConfigBundle;
@:provide var config:Config;
@:provide static var switcher:FrameSwitcher;
@:provide static var storage:EditorStorage;
public function new() {
super(ID);
@@ -43,8 +44,12 @@ using ru.m.tankz.view.ViewUtil;
override public function onShow(data:LevelPack):Void {
super.onShow(data);
type = data.id.type;
levels.data = data.data;
pack = data;
type = pack.id.type;
levels.data = pack.data;
if (pack.data.length > 0) {
level = pack.data[0];
}
}
private function set_type(value:GameType):GameType {
@@ -104,20 +109,14 @@ using ru.m.tankz.view.ViewUtil;
updateSelected(null, point);
}
private function open():Void {
FileUtil.browse().then(function(content:FileContent) {
fileName = content.name;
level = LevelUtil.loads(content.content);
});
}
private function save():Void {
var data = mapView.data;
data.id = level.id;
data.packId = pack.id;
data.name = levelName.text;
FileUtil.save({
name: fileName,
content: LevelUtil.dumps(config, data),
});
pack.data[data.id] = data;
levels.data = pack.data;
storage.write(pack.id, pack);
}
private function applySize():Void {
@@ -129,4 +128,23 @@ using ru.m.tankz.view.ViewUtil;
heightInput.text = Std.string(config.map.grid.height);
mapView.gridSize = null;
}
private function addLevel():Void {
var level = LevelUtil.empty(config.map.grid, 0);
level.id = pack.data.length;
level.packId = pack.id;
pack.data.push(level);
levels.data = pack.data;
this.level = level;
}
private function removeLevel():Void {
if (level != null) {
pack.data.remove(level);
levels.data = pack.data;
if (pack.data.length > 0) {
this.level = pack.data[0];
}
}
}
}

View File

@@ -3,14 +3,20 @@ views:
- $type: haxework.view.group.HGroupView
geometry.stretch: true
views:
- id: levels
$type: haxework.view.list.VListView
geometry.width: 200
- $type: haxework.view.group.VGroupView
geometry.height: 100%
factory: ~levelViewFactory
+onItemSelect: ~onLevelSelect
scroll:
$type: haxework.view.list.VScrollBarView
views:
- id: levels
$type: haxework.view.list.VListView
geometry.width: 200
geometry.height: 100%
factory: ~levelViewFactory
+onItemSelect: ~onLevelSelect
scroll:
$type: haxework.view.list.VScrollBarView
- $type: haxework.view.form.ButtonView
text: +
+onPress: ~addLevel()
- $type: haxework.view.group.VGroupView
geometry.stretch: true
views:
@@ -69,22 +75,20 @@ views:
overflow.x: crop
overflow.y: crop
views:
- $type: ru.m.tankz.view.game.GameViewContainer
views:
- id: mapView
$type: ru.m.tankz.editor.view.map.MapEditView
- $type: ru.m.tankz.view.game.GameViewContainer
views:
- id: mapView
$type: ru.m.tankz.editor.view.map.MapEditView
- $type: haxework.view.group.HGroupView
style: panel
layout.margin: 10
views:
- $type: haxework.view.SpriteView
geometry.width: 100%
- id: openButton
$type: haxework.view.form.ButtonView
text: Open
+onPress: ~open()
- id: saveButton
$type: haxework.view.form.ButtonView
- $type: haxework.view.form.ButtonView
text: Remove
+onPress: ~removeLevel()
- $type: haxework.view.form.ButtonView
text: Save
+onPress: ~save()
- $type: haxework.view.form.ButtonView

View File

@@ -1,20 +1,22 @@
package ru.m.tankz.editor.view;
import haxework.view.data.DataView;
import haxework.view.form.ButtonView;
import haxework.view.frame.FrameSwitcher;
import haxework.view.frame.FrameView;
import ru.m.tankz.bundle.ILevelBundle;
import ru.m.tankz.config.Config;
import ru.m.tankz.editor.FileUtil;
import ru.m.tankz.Type;
import ru.m.tankz.util.LevelUtil;
@:template class PackListFrame extends FrameView<Dynamic> {
public static inline var ID = "pack_list";
@:view("packs") var packView:DataView<LevelPack, ButtonView>;
@:view("packs") var packView:DataView<PackId, PackView>;
@:provide static var levelBundle:ILevelBundle;
@:provide static var switcher:FrameSwitcher;
@:provide static var storage:EditorStorage;
public function new() {
super(ID);
@@ -22,20 +24,49 @@ import ru.m.tankz.Type;
override public function onShow(data:Dynamic):Void {
super.onShow(data);
packView.data = [
levelBundle.get(new PackId("classic")),
levelBundle.get(new PackId("dota")),
levelBundle.get(new PackId("death")),
];
packView.data = storage.packList();
}
private function packViewFactory(index:Int, value:LevelPack):ButtonView {
var result = new ButtonView();
result.text = value.id;
return result;
private function onPackAction(value:PackId, action:String):Void {
switch action {
case "export":
var pack:LevelPack = storage.read(value);
FileUtil.save({
name: '${value}.zip',
content: LevelUtil.pack(pack.data),
});
case "edit":
switcher.change(PackFrame.ID, storage.read(value));
case "delete":
storage.delete(value);
packView.data = storage.packList();
case _:
}
}
private function onPackSelect(value:LevelPack):Void {
switcher.change(PackFrame.ID, value);
private function create():Void {
var levelPack:LevelPack = {
id: new PackId("classic", "modern"),
data: [],
}
storage.write(levelPack.id, levelPack);
packView.data.push(levelPack.id);
packView.data = packView.data;
}
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;
});
}
}

View File

@@ -1,17 +1,22 @@
---
views:
- id: packs
$type: haxework.view.data.DataView
geometry.width: 100%
geometry.height: 100%
geometry.padding: 30
overflow.y: scroll
layout:
$type: haxework.view.layout.TailLayout
rowSize: 10
margin: 5
factory: ~packViewFactory
+onDataSelect: ~onPackSelect
- $type: haxework.view.group.VGroupView
style: container
layout.margin: 10
views:
- $type: haxework.view.form.ButtonView
text: New
+onPress: ~create()
- id: packs
$type: haxework.view.data.DataView
layout:
$type: haxework.view.layout.VerticalLayout
margin: 5
factory: ~ru.m.tankz.editor.view.PackView.factory
+onDataAction: ~onPackAction
- $type: haxework.view.form.ButtonView
text: Open
+onPress: ~open()
- $type: haxework.view.group.HGroupView
style: panel
layout.margin: 10

View File

@@ -0,0 +1,50 @@
package ru.m.tankz.editor.view;
import haxework.signal.Signal;
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;
using ru.m.tankz.view.ViewUtil;
@:template class PackView extends HGroupView implements IListItemView<PackId> {
public var item_index(default, default):Int;
public var data(default, set):PackId;
private var actionSignal(get, null):Signal2<PackId, String>;
@:view var label:ButtonView;
private function set_data(value:PackId):PackId {
data = value;
label.text = data.toPackLabel();
return data;
}
private function get_actionSignal():Signal2<PackId, String> {
var dataView:DataView<PackId, PackView> = cast parent;
return dataView.onDataAction;
}
private function export():Void {
actionSignal.emit(data, "export");
}
private function edit():Void {
actionSignal.emit(data, "edit");
}
private function delete():Void {
actionSignal.emit(data, "delete");
}
public static function factory(index:Int, value:PackId):PackView {
var result = new PackView();
result.item_index = index;
result.data = value;
return result;
}
}

View File

@@ -0,0 +1,14 @@
---
layout.margin: 5
views:
- $type: haxework.view.form.ButtonView
style: button.prev
+onPress: ~export()
- id: label
$type: haxework.view.form.ButtonView
geometry.width: 250
+onPress: ~edit()
- $type: haxework.view.form.ButtonView
style: button.close
+onPress: ~delete()