[client] add SettingsFrame

This commit is contained in:
2018-07-19 17:47:25 +03:00
parent 17289fde92
commit d0c796aab2
22 changed files with 549 additions and 34 deletions

View File

@@ -20,10 +20,14 @@ views:
$type: ru.m.tankz.frame.GameFrame
- id: network
$type: ru.m.tankz.frame.NetworkFrame
- id: settings
$type: ru.m.tankz.frame.SettingsFrame
- $type: haxework.gui.LabelView
$style: label
inLayout: false
contentSize: true
vAlign: BOTTOM
hAlign: RIGHT
rightMargin: 10
bottomMargin: 10
text: "@res:text:version"

View File

@@ -1,5 +1,6 @@
package ru.m.tankz;
import ru.m.tankz.storage.SettingsStorage;
import haxework.provider.Provider;
import haxework.resources.IResources;
import haxework.resources.Resources;
@@ -47,6 +48,7 @@ class Init {
Provider.setFactory(IConfigBundle, ConfigBundle);
Provider.setFactory(SaveStorage, SaveStorage);
Provider.setFactory(UserStorage, UserStorage);
Provider.setFactory(SettingsStorage, SettingsStorage);
Provider.setFactory(SoundManager, SoundManager);
Provider.setFactory(NetworkManager, NetworkManager);
Provider.setFactory(IControlFactory, ClientControlFactory);

View File

@@ -19,3 +19,14 @@ label:
fontFamily: Courirer New
fontSize: 16
shadowColor: 0x000000
close:
inLayout: false
hAlign: LEFT
vAlign: BOTTOM
leftMargin: 10
bottomMargin: 10
contentSize: true
skin:
$type: haxework.gui.skin.ButtonBitmapSkin
image: "@asset:image:resources/image/ui/close.png"

View File

@@ -0,0 +1,86 @@
package ru.m.tankz.control;
import ru.m.geom.Direction;
import ru.m.tankz.control.Control.TankAction;
import yaml.Parser;
import yaml.Renderer;
import yaml.Yaml;
typedef ActionItem = {
public var action: TankAction;
public var key: Int;
}
typedef KeyBinding = Map<Int, TankAction>;
typedef ActionItemRaw = {
public var action: String;
public var key: Int;
}
class ActionConfig {
public var data(default, null): Array<ActionItem>;
public function new(data: Array<ActionItem>) {
this.data = data;
}
public function asKeyBinding(): KeyBinding {
var result = new Map<Int, TankAction>();
for (item in data) {
result[item.key] = item.action;
}
return result;
}
public static function action2string(action: TankAction): String {
return switch (action) {
case TankAction.SHOT: "SHOT";
case TankAction.MOVE(Direction.TOP): "MOVE_TOP";
case TankAction.MOVE(Direction.LEFT): "MOVE_LEFT";
case TankAction.MOVE(Direction.BOTTOM): "MOVE_BOTTOM";
case TankAction.MOVE(Direction.RIGHT): "MOVE_RIGHT";
case _: throw 'Unsupported action "${action}"';
}
}
public static function string2action(value: String): TankAction {
return switch (value) {
case "SHOT": TankAction.SHOT;
case "MOVE_TOP": TankAction.MOVE(Direction.TOP);
case "MOVE_LEFT": TankAction.MOVE(Direction.LEFT);
case "MOVE_BOTTOM": TankAction.MOVE(Direction.BOTTOM);
case "MOVE_RIGHT": TankAction.MOVE(Direction.RIGHT);
case _: throw 'Unsupported value "${value}"';
}
}
public function clone(): ActionConfig {
return loads(dumps());
}
public function dumps(): String {
var raw: Array<ActionItemRaw> = [];
for (item in this.data) {
raw.push({
action: action2string(item.action),
key: item.key,
});
}
return Yaml.render(raw, Renderer.options().setFlowLevel(0));
}
public static function loads(value: String): ActionConfig {
var raw: Array<ActionItemRaw> = Yaml.parse(value, Parser.options().useObjects());
var data: Array<ActionItem> = [];
for (item in raw) {
data.push({
action: string2action(item.action),
key: item.key,
});
}
return new ActionConfig(data);
}
}

View File

@@ -5,22 +5,23 @@ import flash.events.KeyboardEvent;
import flash.Lib;
import flash.ui.Keyboard;
import haxe.Timer;
import ru.m.geom.Direction;
import ru.m.tankz.control.ActionConfig;
import ru.m.tankz.control.Control;
import ru.m.tankz.storage.SettingsStorage;
import ru.m.tankz.Type;
typedef KeyBinding = Map<Int, TankAction>;
class HumanControl extends Control {
@:provide var settings: SettingsStorage;
private var keyBinding:KeyBinding;
private var moveQueue:Array<Int>;
private var shotTimer:Timer;
public function new(playerId:PlayerId, controlIndex:Int) {
super(playerId);
this.keyBinding = resolve(controlIndex);
this.keyBinding = settings.read(controlIndex).asKeyBinding();
moveQueue = new Array<Int>();
Lib.current.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
Lib.current.stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
@@ -81,27 +82,4 @@ class HumanControl extends Control {
private function shot():Void {
action(TankAction.SHOT);
}
private static function resolve(controlIndex:Int):KeyBinding {
switch (controlIndex) {
case 0:
return [
Keyboard.A => TankAction.MOVE(Direction.LEFT),
Keyboard.S => TankAction.MOVE(Direction.BOTTOM),
Keyboard.W => TankAction.MOVE(Direction.TOP),
Keyboard.D => TankAction.MOVE(Direction.RIGHT),
Keyboard.SPACE => TankAction.SHOT
];
case 1:
return [
Keyboard.LEFT => TankAction.MOVE(Direction.LEFT),
Keyboard.DOWN => TankAction.MOVE(Direction.BOTTOM),
Keyboard.UP => TankAction.MOVE(Direction.TOP),
Keyboard.RIGHT => TankAction.MOVE(Direction.RIGHT),
Keyboard.NUMPAD_0 => TankAction.SHOT
];
case x:
throw 'Invalid control index ${x}';
}
}
}

View File

@@ -37,19 +37,24 @@ class GameFrame extends VGroupView {
start(Provider.get(GameSave));
}
private function connectGame(game: Game) {
game.engine.connect(render);
game.engine.connect(Provider.get(SoundManager));
}
private function start(save:GameSave):Void {
switch (save.server) {
case GameServer.LOCAL:
game = new Game(save.state.type);
connectGame(game);
game.start(save).then(onGameStateChange).endThen(onGameComplete);
timer = new Timer(10);
timer.run = updateEngine;
case GameServer.NETWORK:
game = new NetworkGame(save.state.type);
connectGame(game);
network.game = cast game;
}
game.engine.connect(render);
game.engine.connect(Provider.get(SoundManager));
content.addEventListener(Event.ENTER_FRAME, redraw);
render.draw(game.engine);
state.text = stateString(game);

View File

@@ -0,0 +1,24 @@
package ru.m.tankz.frame;
import haxework.gui.frame.IFrameSwitcher;
import haxework.gui.ButtonView;
import haxework.gui.VGroupView;
@:template("ru/m/tankz/frame/SettingsFrame.yaml", "ru/m/tankz/Style.yaml")
class SettingsFrame extends VGroupView {
public static var ID(default, never):String = "settings";
@:provide var frameSwitcher:IFrameSwitcher;
@:view var close:ButtonView;
private function init():Void {
close.onPress = this;
}
public function onPress(_):Void {
frameSwitcher.change(StartFrame.ID);
}
}

View File

@@ -0,0 +1,24 @@
---
pWidth: 100
pHeight: 100
views:
- $type: haxework.gui.LabelView
$style: label
pWidth: 100
height: 20
text: Settings
- $type: haxework.gui.HGroupView
pWidth: 100
pHeight: 100
views:
- $type: ru.m.tankz.frame.settings.SettingsEditor
pWidth: 50
pHeight: 100
controlIndex: 0
- $type: ru.m.tankz.frame.settings.SettingsEditor
pWidth: 50
pHeight: 100
controlIndex: 1
- id: close
$type: haxework.gui.ButtonView
$style: close

View File

@@ -23,6 +23,7 @@ class StartFrame extends VGroupView {
@:view var dota_2p_coop(default, null):ButtonView;
@:view var dota_2p_vs(default, null):ButtonView;
@:view var network(default, null):ButtonView;
@:view var settings(default, null):ButtonView;
@:provide var frameSwitcher:IFrameSwitcher;
@:provide var storage:SaveStorage;
@@ -35,6 +36,7 @@ class StartFrame extends VGroupView {
dota_2p_coop.onPress = this;
dota_2p_vs.onPress = this;
network.onPress = this;
settings.onPress = this;
}
public function onShow():Void {
@@ -63,6 +65,8 @@ class StartFrame extends VGroupView {
startGame(DotaGame.TYPE, DotaGame.PLAYER2_VS);
case 'network':
frameSwitcher.change(NetworkFrame.ID);
case 'settings':
frameSwitcher.change(SettingsFrame.ID);
}
}

View File

@@ -70,3 +70,15 @@ views:
$type: haxework.gui.ButtonView
text: Network
$style: button
# settings
- id: settings
$type: haxework.gui.ButtonView
inLayout: false
hAlign: LEFT
vAlign: BOTTOM
leftMargin: 10
bottomMargin: 10
contentSize: true
skin:
$type: haxework.gui.skin.ButtonBitmapSkin
image: "@asset:image:resources/image/ui/settings.png"

View File

@@ -0,0 +1,88 @@
package ru.m.tankz.frame.settings;
import haxework.gui.ButtonView;
import haxework.gui.HGroupView;
import haxework.gui.LabelView;
import haxework.gui.list.ListView.IListItemView;
import haxework.gui.skin.ColorSkin;
import openfl.Assets;
import openfl.events.KeyboardEvent;
import promhx.Deferred;
import promhx.Promise;
import ru.m.tankz.control.ActionConfig;
import ru.m.tankz.control.Control;
class KeyboardMap {
private var data:Map<Int, String>;
public function new() {
this.data = new Map();
var data = Assets.getText("resources/keyboard.txt");
for (line in data.split("\n")) {
var arr = line.split("\t");
if (arr.length == 2) {
this.data.set(Std.parseInt(arr[1]), arr[0]);
}
}
}
private static var instance: KeyboardMap;
public static function getName(key: Int): String {
if (instance == null) instance = new KeyboardMap();
return key == -1 ? "<NONE>" : instance.data.exists(key) ? instance.data.get(key) : Std.string(key);
}
}
@:template("ru/m/tankz/frame/settings/ActionView.yaml", "ru/m/tankz/Style.yaml")
class ActionView extends HGroupView implements IListItemView<ActionItem> {
public var item_index(default, default):Int;
public var data(default, set):ActionItem;
@:view var action(default, null):LabelView;
@:view var key(default, null):LabelView;
private var editDeferred: Deferred<Int>;
private function init():Void {
}
private static function actionLabel(action: TankAction): String {
return ActionConfig.action2string(action);
}
private static function keyLabel(key: Int): String {
return KeyboardMap.getName(key);
}
private function set_data(value:ActionItem):ActionItem {
data = value;
action.text = actionLabel(data.action);
key.text = keyLabel(data.key);
return data;
}
public function edit():Promise<Int> {
cast(this.skin, ColorSkin).color = 0x00ff00;
invalidate();
editDeferred = new Deferred();
content.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
return editDeferred.promise();
}
private function onKeyDown(event: KeyboardEvent):Void {
content.stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
cast(this.skin, ColorSkin).color = 0x000000;
invalidate();
data.key = event.keyCode;
key.text = keyLabel(data.key);
editDeferred.resolve(data.key);
editDeferred = null;
}
}

View File

@@ -0,0 +1,21 @@
---
width: 440
height: 44
margins: 5
views:
- id: action
$type: haxework.gui.LabelView
$style: label
pWidth: 50
pHeight: 100
text: ""
- id: key
$type: haxework.gui.LabelView
$style: label
pWidth: 50
pHeight: 100
text: ""
skin:
$type: haxework.gui.skin.ColorSkin
color: "#000000"
alpha: 0.2

View File

@@ -0,0 +1,75 @@
package ru.m.tankz.frame.settings;
import haxework.gui.LabelView;
import ru.m.tankz.control.ActionConfig;
import promhx.Promise;
import ru.m.tankz.storage.SettingsStorage;
import haxework.gui.ButtonView;
import ru.m.tankz.control.ActionConfig.ActionItem;
import haxework.gui.list.ListView;
import haxework.gui.VGroupView;
@:template("ru/m/tankz/frame/settings/SettingsEditor.yaml", "ru/m/tankz/Style.yaml")
class SettingsEditor extends VGroupView {
public var controlIndex(default, set): Int;
@:view var label:LabelView;
@:view var list:ListView<ActionItem>;
@:view var change:ButtonView;
@:view var clear:ButtonView;
@:view var reset:ButtonView;
@:provide var storage: SettingsStorage;
private function init():Void {
change.onPress = this;
clear.onPress = this;
reset.onPress = this;
}
private function set_controlIndex(value: Int): Int {
this.controlIndex = value;
label.text = 'Player ${controlIndex+1}';
list.data = storage.read(controlIndex).data;
return this.controlIndex;
}
public function onPress(view:ButtonView):Void {
switch (view.id) {
case "change": _change();
case "clear": _clear();
case "reset": _reset();
case _:
}
}
private function _change():Void {
var p: Promise<Int> = Promise.promise(0);
for (view in list.items) {
var v: ActionView = cast view;
if (v.data == null) break;
p = p.pipe(function(_):Promise<Int> return v.edit());
}
p.then(function(_) _save());
}
private function _clear():Void {
for (item in list.data) {
item.key = -1;
}
list.invalidate();
_save();
}
private function _reset():Void {
list.data = SettingsStorage.getDefault(controlIndex).data;
list.invalidate();
_save();
}
private function _save():Void {
storage.write(controlIndex, new ActionConfig(list.data));
}
}

View File

@@ -0,0 +1,27 @@
$type: haxework.gui.VGroupView
layoutMargin: 10
views:
- id: label
$type: haxework.gui.LabelView
$style: label
- id: change
$type: haxework.gui.ButtonView
$style: button
text: Change
- id: clear
$type: haxework.gui.ButtonView
$style: button
text: Clear
- id: reset
$type: haxework.gui.ButtonView
$style: button
text: Reset
- id: list
$type: haxework.gui.list.VListView<ru.m.tankz.control.ActionItem>
factory: "@class:ru.m.tankz.frame.settings.ActionView"
pWidth: 100
pHeight: 100
scroll:
$type: haxework.gui.list.VScrollView
width: 1
pHeight: 100

View File

@@ -0,0 +1,55 @@
package ru.m.tankz.storage;
import flash.net.SharedObject;
import flash.ui.Keyboard;
import ru.m.geom.Direction;
import ru.m.tankz.control.ActionConfig;
import ru.m.tankz.control.Control.TankAction;
class SettingsStorage {
private static var TAG(default, never):String = 'SettingsStorage';
private var so:SharedObject;
public function new() {
so = SharedObject.getLocal('settings');
}
public function read(index: Int):Null<ActionConfig> {
var data:String = Reflect.getProperty(so.data, Std.string(index));
L.d(TAG, 'read: ${data}');
if (data != null) {
return ActionConfig.loads(data);
}
return getDefault(index);
}
public function write(index: Int, data: ActionConfig):Void {
L.d(TAG, 'write: ${data}');
so.setProperty(Std.string(index), data.dumps());
so.flush();
}
public static function getDefault(index: Int): ActionConfig {
return defaults.get(index).clone();
}
private static var defaults: Map<Int, ActionConfig> = [
0 => new ActionConfig([
{action:TankAction.MOVE(Direction.TOP), key:Keyboard.W},
{action:TankAction.MOVE(Direction.LEFT), key:Keyboard.A},
{action:TankAction.MOVE(Direction.BOTTOM), key:Keyboard.S},
{action:TankAction.MOVE(Direction.RIGHT), key:Keyboard.D},
{action:TankAction.SHOT, key:Keyboard.SPACE},
]),
1 => new ActionConfig([
{action:TankAction.MOVE(Direction.TOP), key:Keyboard.UP},
{action:TankAction.MOVE(Direction.LEFT), key:Keyboard.LEFT},
{action:TankAction.MOVE(Direction.BOTTOM), key:Keyboard.RIGHT},
{action:TankAction.MOVE(Direction.RIGHT), key:Keyboard.DOWN},
{action:TankAction.SHOT, key:Keyboard.NUMPAD_0},
]),
];
}