From 94a8d8175115c841aefbc192a4fa3870a4f7be9b Mon Sep 17 00:00:00 2001 From: inb Date: Sun, 23 Apr 2023 16:46:58 +0100 Subject: [PATCH 1/6] Render islands in leaflet --- cc2me/savedata/constants.py | 35 ++++++++++++++++- cc2me/ui/icons/dupe.png | Bin 0 -> 315 bytes cc2me/ui/icons/dupe.xcf | Bin 0 -> 3557 bytes cc2me/ui/saveslotchooser.py | 18 ++------- cc2me/ui/tool.py | 7 ++-- cc2me/web/__init__.py | 0 cc2me/web/__main__.py | 3 ++ cc2me/web/server.py | 69 +++++++++++++++++++++++++++++++++ cc2me/web/templates/index.html | 13 +++++++ cc2me/web/templates/map.html | 43 ++++++++++++++++++++ cc2me/web/templates/slots.html | 16 ++++++++ cc2me/web/tool.py | 13 +++++++ cc2me/web/utils.py | 35 +++++++++++++++++ setup.py | 4 +- 14 files changed, 236 insertions(+), 20 deletions(-) create mode 100644 cc2me/ui/icons/dupe.png create mode 100644 cc2me/ui/icons/dupe.xcf create mode 100644 cc2me/web/__init__.py create mode 100644 cc2me/web/__main__.py create mode 100644 cc2me/web/server.py create mode 100644 cc2me/web/templates/index.html create mode 100644 cc2me/web/templates/map.html create mode 100644 cc2me/web/templates/slots.html create mode 100644 cc2me/web/tool.py create mode 100644 cc2me/web/utils.py diff --git a/cc2me/savedata/constants.py b/cc2me/savedata/constants.py index e05a636..559ab07 100644 --- a/cc2me/savedata/constants.py +++ b/cc2me/savedata/constants.py @@ -1,7 +1,10 @@ +import os import random from abc import ABC from enum import Enum -from typing import cast, List, Optional, Union +from typing import cast, List, Optional, Union, Dict +from xml.etree import ElementTree +from xml.etree.ElementTree import Element MAX_INTEGER = 4294967295 @@ -508,3 +511,33 @@ def get_default_state(v_type: VehicleType) -> List[Capacity]: if v_type in item.vtypes: values.append(item) return values + + +def get_cc2_appdata() -> str: + cc2dir = os.path.expandvars(r'%APPDATA%\\Carrier Command 2') + return cc2dir + + +def get_persistent_file_path() -> str: + persistent = os.path.join(get_cc2_appdata(), "persistent_data.xml") + return persistent + + +def read_save_slots(slots_file: Optional[str] = None) -> List[Dict[str, str]]: + if slots_file is None: + slots_file = get_persistent_file_path() + slots = [] + with open(slots_file, "r") as xmlfile: + xml = xmlfile.read() + etree = ElementTree.fromstring(xml) + for item in etree: + if isinstance(item, Element): + filename = item.attrib.get("save_name") + text = item.attrib.get("display_name") + if filename: + slots.append({ + "filename": filename, + "display": text + }) + return slots + diff --git a/cc2me/ui/icons/dupe.png b/cc2me/ui/icons/dupe.png new file mode 100644 index 0000000000000000000000000000000000000000..730c1d25d2fea2f1b61ea1b07d9c3c5d7024d8d2 GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjY)RhkE)4%caKYZ?lYt_f1s;*b z3=G`DAk4@xYYs>cdx@v7EBiAZCSfkF^sH!v1 z)|k9E=JlKj_ASfZW`r?MQVV@5%*z)Z^5EEY=J{b>b}dhKC4v0l>FVdQ&MBb@01Q)d A(EtDd literal 0 HcmV?d00001 diff --git a/cc2me/ui/icons/dupe.xcf b/cc2me/ui/icons/dupe.xcf new file mode 100644 index 0000000000000000000000000000000000000000..995655b1ee5e40f1f18f548ffcb97cb321e6e94c GIT binary patch literal 3557 zcmeHJ&2G~`5MG=7_@`D&p@0yFmAIq_qgL<)6oil(sb_AEW4kLkab&w9IaS<|fCCrA zt?CmXBp!lS;KXB~beXYtqjhj9qEeJYMjG$8Gqbzn@yw1RHW<;dAJUM;0Rg%v81YSD zO@qfRVB_g~LXd%O12=&02u6%{J1{(l(TQMN*G~q6V3@QMYm(~}ZET6;y&61e)cdbqs`AM9lv{MXj8udK7_h_%68n7=^yRfuZ zvsCKoVt`A>*~s(RFrrW7epY8vBNHO`WXN*LhW>Go;p!v|vml?j2cpTJqEb>_ict`x zeS=tkC#3y@5bLLu!U(}GJ^%)O>&VM6w1?g#Na@3UG}G|`)B?s2#DNWgag8Z34CH}= zEd|>O-cYck;7!1|uRN!%=+s03F_Jzm$C>}&oBp;c(?8?fB|85_I44}=)*QY8bBmb3 z8gBwyWD0f-K84pbuY~1(xVO&BbL#zgwf1m9%$JGbU1IYV+2oBHZh_8F_%gd^m@@MA z!u)pBnn0yY(CaP)JPWC=5U?NwHQxvzorXeaeyqxx=Qw|?B)>Cv zm2mkH%e~7&m_J(<=PCKL_HkjXmy6?lNOu!TpNdKh*IOkK~3plT? zA8ex|CD|VYQ2K#g-2WgKGW#pqgmXPW-oto5uR=cv*B;J(jH*5O?|VT=y8oyXbjfeZ CUokBJ literal 0 HcmV?d00001 diff --git a/cc2me/ui/saveslotchooser.py b/cc2me/ui/saveslotchooser.py index dc2ffb9..d11aa0e 100644 --- a/cc2me/ui/saveslotchooser.py +++ b/cc2me/ui/saveslotchooser.py @@ -5,6 +5,8 @@ from xml.etree.ElementTree import Element from tkinter import simpledialog +from cc2me.savedata.constants import read_save_slots + class SlotChooser(simpledialog.Dialog): @@ -22,22 +24,10 @@ def body(self, master) -> None: command=callback) btn.pack(fill=tkinter.X) - def __init__(self, app, persistent_file: str, choice: dict, title="Load a save slot", ): + def __init__(self, app, persistent_file: str, choice: dict, title="Load a save slot"): self.persistent_file = persistent_file - self.slots = [] + self.slots = read_save_slots(persistent_file) self.choice = choice - with open(self.persistent_file, "r") as xmlfile: - xml = xmlfile.read() - self.etree = ElementTree.fromstring(xml) - for item in self.etree: - if isinstance(item, Element): - filename = item.attrib.get("save_name") - text = item.attrib.get("display_name") - if filename: - self.slots.append({ - "filename": filename, - "display": text - }) super(SlotChooser, self).__init__(parent=app, title=title) diff --git a/cc2me/ui/tool.py b/cc2me/ui/tool.py index d392b81..66ab49e 100644 --- a/cc2me/ui/tool.py +++ b/cc2me/ui/tool.py @@ -8,7 +8,7 @@ from typing import Optional, List from .properties import Properties -from ..savedata.constants import get_island_name, VehicleType, VehicleAttachmentDefinitionIndex +from ..savedata.constants import VehicleType, VehicleAttachmentDefinitionIndex, get_persistent_file_path, get_cc2_appdata from ..savedata.types.objects import Island, Unit, get_unit, Spawn from ..savedata.types.tiles import Tile from ..savedata.loader import CC2XMLSave, load_save_file @@ -25,8 +25,8 @@ class App(tkinter.Tk): WIDTH = 900 HEIGHT = 750 - cc2dir = os.path.expandvars(r'%APPDATA%\\Carrier Command 2') - persistent = os.path.join(cc2dir, "persistent_data.xml") + cc2dir = get_cc2_appdata() + persistent = get_persistent_file_path() def __init__(self, *args, **kwargs): tkinter.Tk.__init__(self, *args, **kwargs) @@ -239,7 +239,6 @@ def duplicate_selected(self): if new_units: self.select_markers(new_units) - def add_new_seal(self): self.add_new_unit(VehicleType.Seal) diff --git a/cc2me/web/__init__.py b/cc2me/web/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cc2me/web/__main__.py b/cc2me/web/__main__.py new file mode 100644 index 0000000..be01b2b --- /dev/null +++ b/cc2me/web/__main__.py @@ -0,0 +1,3 @@ +from .tool import run + +run() \ No newline at end of file diff --git a/cc2me/web/server.py b/cc2me/web/server.py new file mode 100644 index 0000000..692223f --- /dev/null +++ b/cc2me/web/server.py @@ -0,0 +1,69 @@ +"""Flask webserver for CC2E""" +from pathlib import Path +from flask import Flask, render_template +from functools import lru_cache +from ..savedata.constants import read_save_slots, get_cc2_appdata, get_island_name +from ..savedata.loader import CC2XMLSave, load_save_file +from .utils import loc_to_geo_box, loc_to_geo + +WEB_DIR = Path(__file__).parent.absolute() +STATIC_DIR = WEB_DIR / "static" +TEMPLATE_DIR = WEB_DIR / "templates" + + +app = Flask("CC2E") +app.template_folder = TEMPLATE_DIR +app.static_folder = STATIC_DIR + + +@app.route("/") +def home(): + return render_template("index.html") + + +@app.route("/slots") +def slots_route(): + slots = read_save_slots() + return render_template("slots.html", slots=slots) + + +@lru_cache(4) +def get_map_slot(slot) -> CC2XMLSave: + path = Path(get_cc2_appdata()) / "saved_games" / slot / "save.xml" + save = load_save_file(path) + return save + + +@app.route("/geo//") +def geojson_route(slot, layer): + save = get_map_slot(slot) + geo = { + "type": "FeatureCollection", + "features": [], + } + if layer == "tiles": + + for tile in save.tiles: + item = { + "type": "Feature", + "id": tile.id, + "properties": { + "name": get_island_name(tile.id), + "team": tile.team_control, + "difficulty": tile.difficulty_factor, + "facility.category": tile.facility.category, + }, + "geometry": { + "type": "Polygon", + "coordinates": [loc_to_geo_box(tile.loc, tile.island_radius, tile.island_radius)] + } + } + geo["features"].append(item) + + return geo + + +@app.route("/map/") +def map_route(slot): + save = get_map_slot(slot) + return render_template("map.html", save=save, slot=slot) diff --git a/cc2me/web/templates/index.html b/cc2me/web/templates/index.html new file mode 100644 index 0000000..50021a3 --- /dev/null +++ b/cc2me/web/templates/index.html @@ -0,0 +1,13 @@ + + + + + CC2 Editor + + +

Carrier Command 2 Save Editor (CC2E)

+ + + \ No newline at end of file diff --git a/cc2me/web/templates/map.html b/cc2me/web/templates/map.html new file mode 100644 index 0000000..aa1d871 --- /dev/null +++ b/cc2me/web/templates/map.html @@ -0,0 +1,43 @@ + + + + + CC2 Map {{ filename }} - {{ displayname }} + + + + + + + +
+ + + \ No newline at end of file diff --git a/cc2me/web/templates/slots.html b/cc2me/web/templates/slots.html new file mode 100644 index 0000000..4829244 --- /dev/null +++ b/cc2me/web/templates/slots.html @@ -0,0 +1,16 @@ + + + + + Save Slot Selection + + +

Select a slot to work on

+ + + + \ No newline at end of file diff --git a/cc2me/web/tool.py b/cc2me/web/tool.py new file mode 100644 index 0000000..a55581c --- /dev/null +++ b/cc2me/web/tool.py @@ -0,0 +1,13 @@ +"""Web server entrypoint for CC2 Editor""" + +import argparse +from typing import Optional, List +from . import server + +parser = argparse.ArgumentParser(description=__doc__) + + +def run(args: Optional[List[str]] = None) -> None: + parser.parse_args(args) + server.app.run("127.0.0.1", port=4422, debug=True) + diff --git a/cc2me/web/utils.py b/cc2me/web/utils.py new file mode 100644 index 0000000..6a37b75 --- /dev/null +++ b/cc2me/web/utils.py @@ -0,0 +1,35 @@ +from typing import Tuple, List +from dataclasses import dataclass +from ..savedata.types.utils import Location + +SCALE_MAP_COORDS = 10000 + + +@dataclass +class GPoint: + lon: float + lat: float + + def move(self, dx: float = 0, dy: float = 0) -> "GPoint": + self.lon += dx + self.lat += dy + return self + + def as_list(self): + return [self.lon, self.lat] + + +def loc_to_geo(loc: Location) -> GPoint: + return GPoint(loc.x / SCALE_MAP_COORDS, loc.z / SCALE_MAP_COORDS) + + +def loc_to_geo_box(loc: Location, w: float, h: float) -> List: + height = h / SCALE_MAP_COORDS + width = w / SCALE_MAP_COORDS + return [ + loc_to_geo(loc).move(dx=width / -2, dy=height / -2).as_list(), + loc_to_geo(loc).move(dx=width / 2, dy=height / -2).as_list(), + loc_to_geo(loc).move(dx=width / 2, dy=height / 2).as_list(), + loc_to_geo(loc).move(dx=width / -2, dy=height / 2).as_list(), + loc_to_geo(loc).move(dx=width / -2, dy=height / -2).as_list(), + ] diff --git a/setup.py b/setup.py index b317339..42e46de 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,8 @@ requirements = [ "tkintermapview==1.15", "pillow==9.2.0", + "pandas", + "flask", ] setup( @@ -25,7 +27,7 @@ "cc2me = cc2me.ui.tool:run" ], "console_scripts": [ - "cc2mec = cc2me.ui.tool:run" + "cce = cc2me.web.tool:run" ] }, ) From 4459189e39bcd4e51854314a64dcb8671c670436 Mon Sep 17 00:00:00 2001 From: inb Date: Sun, 23 Apr 2023 19:50:35 +0100 Subject: [PATCH 2/6] Drag islands around --- cc2me/web/server.py | 31 ++++++++++++++++++++++++--- cc2me/web/static/Path.Drag.min.js | 8 +++++++ cc2me/web/static/ocean.png | Bin 0 -> 133 bytes cc2me/web/templates/map.html | 34 ++++++++++++++++++++++++------ cc2me/web/utils.py | 14 +++++++++++- 5 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 cc2me/web/static/Path.Drag.min.js create mode 100644 cc2me/web/static/ocean.png diff --git a/cc2me/web/server.py b/cc2me/web/server.py index 692223f..cefebf1 100644 --- a/cc2me/web/server.py +++ b/cc2me/web/server.py @@ -1,10 +1,12 @@ """Flask webserver for CC2E""" from pathlib import Path -from flask import Flask, render_template + +from flask import Flask, render_template, request from functools import lru_cache from ..savedata.constants import read_save_slots, get_cc2_appdata, get_island_name from ..savedata.loader import CC2XMLSave, load_save_file -from .utils import loc_to_geo_box, loc_to_geo +from ..ui.cc2constants import get_team_color +from .utils import loc_to_geo_box, loc_to_geo, cc2_to_minutes, latlong_to_cc2loc WEB_DIR = Path(__file__).parent.absolute() STATIC_DIR = WEB_DIR / "static" @@ -48,14 +50,18 @@ def geojson_route(slot, layer): "type": "Feature", "id": tile.id, "properties": { + "kind": "tile", "name": get_island_name(tile.id), "team": tile.team_control, + "team_color": get_team_color(tile.team_control), "difficulty": tile.difficulty_factor, "facility.category": tile.facility.category, + "radius": cc2_to_minutes(tile.island_radius), + "loc": loc_to_geo(tile.loc), }, "geometry": { "type": "Polygon", - "coordinates": [loc_to_geo_box(tile.loc, tile.island_radius, tile.island_radius)] + "coordinates": loc_to_geo_box(tile.loc, tile.island_radius, tile.island_radius) } } geo["features"].append(item) @@ -67,3 +73,22 @@ def geojson_route(slot, layer): def map_route(slot): save = get_map_slot(slot) return render_template("map.html", save=save, slot=slot) + + +@app.route("/move///", methods=["POST"]) +def move_item(slot: str, kind: str, item: int): + data = request.json + moved = latlong_to_cc2loc(data["lat"], data["lon"]) + save = get_map_slot(slot) + if kind == "tile": + island = save.tile(item) + previous = island.loc + island.move(moved.x, island.loc.y, moved.z) + # move all island-related things (spawns etc) by the same amount + dx = previous.x - moved.x + dz = previous.z - moved.z + + return { + "dx": dx, + "dz": dz + } diff --git a/cc2me/web/static/Path.Drag.min.js b/cc2me/web/static/Path.Drag.min.js new file mode 100644 index 0000000..7a2f7fc --- /dev/null +++ b/cc2me/web/static/Path.Drag.min.js @@ -0,0 +1,8 @@ +/** + * Minified by jsDelivr using UglifyJS v3.1.10. + * Original file: /npm/leaflet.path.drag@0.0.6/src/Path.Drag.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +"use strict";L.PathDraggable=L.Draggable.extend({initialize:function(t){this._path=t,this._canvas=t._map.getRenderer(t)instanceof L.Canvas;var a=this._canvas?this._path._map.getRenderer(this._path)._container:this._path._path;L.Draggable.prototype.initialize.call(this,a,a,!0)},_updatePosition:function(){var t={originalEvent:this._lastEvent};this.fire("drag",t)},_onDown:function(t){var a=t.touches?t.touches[0]:t;this._startPoint=new L.Point(a.clientX,a.clientY),this._canvas&&!this._path._containsPoint(this._path._map.mouseEventToLayerPoint(a))||L.Draggable.prototype._onDown.call(this,t)}}),L.Handler.PathDrag=L.Handler.extend({initialize:function(t){this._path=t},getEvents:function(){return{dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd}},addHooks:function(){this._draggable||(this._draggable=new L.PathDraggable(this._path)),this._draggable.on(this.getEvents(),this).enable(),L.DomUtil.addClass(this._draggable._element,"leaflet-path-draggable")},removeHooks:function(){this._draggable.off(this.getEvents(),this).disable(),L.DomUtil.removeClass(this._draggable._element,"leaflet-path-draggable")},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){this._startPoint=this._draggable._startPoint,this._path.closePopup().fire("movestart").fire("dragstart")},_onDrag:function(t){var a=this._path,n=t.originalEvent.touches&&1===t.originalEvent.touches.length?t.originalEvent.touches[0]:t.originalEvent,i=L.point(n.clientX,n.clientY),e=a._map.layerPointToLatLng(i);this._offset=i.subtract(this._startPoint),this._startPoint=i,this._path.eachLatLng(this.updateLatLng,this),a.redraw(),t.latlng=e,t.offset=this._offset,a.fire("drag",t),t.latlng=this._path.getCenter?this._path.getCenter():this._path.getLatLng(),a.fire("move",t)},_onDragEnd:function(t){this._path._bounds&&this.resetBounds(),this._path.fire("moveend").fire("dragend",t)},latLngToLayerPoint:function(t){return this._path._map.project(L.latLng(t))._subtract(this._path._map.getPixelOrigin())},updateLatLng:function(t){var a=this.latLngToLayerPoint(t);a._add(this._offset);var n=this._path._map.layerPointToLatLng(a);t.lat=n.lat,t.lng=n.lng},resetBounds:function(){this._path._bounds=new L.LatLngBounds,this._path.eachLatLng(function(t){this._bounds.extend(t)})}}),L.Path.include({eachLatLng:function(t,a){a=a||this;var n=function(i){for(var e=0;ek44ofy`glX(f`u%tWsIx;Y9 z?C1WI$O`0h7I;J!GcX91fH32|H2Dgkpp>VJV~B_M+k=d}K;EGR-~HdF@hLDcG^}Ri XQD9&+Q{q|+6ld^s^>bP0l+XkKazh^m literal 0 HcmV?d00001 diff --git a/cc2me/web/templates/map.html b/cc2me/web/templates/map.html index aa1d871..08572aa 100644 --- a/cc2me/web/templates/map.html +++ b/cc2me/web/templates/map.html @@ -10,6 +10,7 @@ +