From 8e9b3ed68d72bd1c5f3e4a644b69d425e85f8641 Mon Sep 17 00:00:00 2001 From: Mario Castro Date: Tue, 5 Nov 2024 00:35:04 +0100 Subject: [PATCH] Auto Vassal module --- .gitignore | 2 + card.go | 3 - cmd/main.go | 11 +- cmd/vassal.go | 20 ++- counter.go | 38 ++--- counter_prototype.go | 9 +- counter_template.go | 33 +++-- counter_test.go | 4 +- fsops/file.go | 33 ++++- output/counters_to_png.go | 25 +++- output/counters_to_vassal.go | 212 ++++++++++++--------------- output/csv_to_vassal.go | 222 +++++++++++++++++++++++++++++ output/zip.go | 13 +- pipelines/csv_to_vassal.go | 52 ------- server/main.go | 5 +- vassal.go | 269 ++++++++++++++++++++++++++++++++--- vassal/buildFile.xml | 65 +++++++++ vassal/buildFile_old.xml | 73 ++++++++++ vassal/embed.go | 41 ++++++ vassal/moduledata | 10 ++ 20 files changed, 881 insertions(+), 259 deletions(-) create mode 100644 output/csv_to_vassal.go delete mode 100644 pipelines/csv_to_vassal.go create mode 100644 vassal/buildFile.xml create mode 100644 vassal/buildFile_old.xml create mode 100644 vassal/embed.go create mode 100644 vassal/moduledata diff --git a/.gitignore b/.gitignore index b1b4a3a..69a6bad 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ server/static/img.png server/tmp .vscode + +vassal_modules diff --git a/card.go b/card.go index 93f63b2..0bb3e95 100644 --- a/card.go +++ b/card.go @@ -58,10 +58,7 @@ func (c *Card) ToCanvas(template *CardsTemplate) (*gg.Context, error) { isLastAreaOfCard := areaIndex != numberOfAreas c.Areas[areaIndex].Height = int(math.Floor(areasHeights[areaIndex])) - // area.Width = (template.Width) - int(template.Margins*2) - // areaCanvas, err := c.processAreav2(template, &area, c.Areas[areaIndex].Height, isLastAreaOfCard) areaCanvas, err := c.ProcessAreav2(&areaCounter, template, int(math.Floor(areasHeights[areaIndex])), isLastAreaOfCard) - // areaCanvas, err := area.Canvas(false) if err != nil { return nil, err } diff --git a/cmd/main.go b/cmd/main.go index 661f62f..83d1d55 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -3,7 +3,6 @@ package main import ( "flag" "os" - "runtime/pprof" "github.com/alecthomas/kong" "github.com/charmbracelet/log" @@ -19,7 +18,7 @@ var Cli struct { Json JsonOutput `cmd:"" help:"Generate a JSON of some short, by transforming another JSON as input"` // Vassal is used to generate a Vassal module for testing purposes. - Vassal vassal `cmd:"" help:"Create a vassal module for testing. It searches for the 'template.xml' in the same folder"` //FIXME + Vassal vassalCli `cmd:"" help:"Create a vassal module for testing. It searches for the 'template.xml' in the same folder"` //FIXME GenerateTemplate GenerateTemplate `cmd:"" help:"Generates a new counter template file with default values"` @@ -28,12 +27,6 @@ var Cli struct { func main() { flag.Parse() - f, err := os.Create("cpu.prof") - if err != nil { - log.Fatal(err) - } - pprof.StartCPUProfile(f) - defer pprof.StopCPUProfile() logger.SetReportTimestamp(false) logger.SetReportCaller(false) @@ -41,7 +34,7 @@ func main() { ctx := kong.Parse(&Cli) - err = ctx.Run() + err := ctx.Run() ctx.FatalIfErrorf(err) logger.Info("Done") diff --git a/cmd/vassal.go b/cmd/vassal.go index 4d10eb1..d0c8ba9 100644 --- a/cmd/vassal.go +++ b/cmd/vassal.go @@ -2,13 +2,23 @@ package main import ( "github.com/alecthomas/kong" - "github.com/sayden/counters/pipelines" + "github.com/sayden/counters/output" ) -type vassal struct { - pipelines.VassalConfig +type vassalCli struct { + Templates []string `help:"List of templates to embed in the Vassal module" short:"t"` + OutputPath string `help:"Path to the temp folder" short:"o"` + output.VassalConfig } -func (i *vassal) Run(ctx *kong.Context) error { - return pipelines.CSVToVassalFile(i.VassalConfig) +func (i *vassalCli) Run(ctx *kong.Context) error { + if i.Templates != nil { + return i.TemplatesToVassal() + } + + return output.CSVToVassalFile(i.VassalConfig) +} + +func (v *vassalCli) TemplatesToVassal() error { + return output.VassalModule(v.OutputPath, v.Templates) } diff --git a/counter.go b/counter.go index 06987de..b724d95 100644 --- a/counter.go +++ b/counter.go @@ -9,11 +9,20 @@ import ( "text/template" "github.com/fogleman/gg" - "github.com/google/uuid" "github.com/pkg/errors" "github.com/thehivecorporation/log" ) +func init() { + var err error + pieceSlotTemplate, err = template.New("piece_text").Parse(Template_NewVassalPiece) + if err != nil { + panic(fmt.Errorf("could not parse template string %w", err)) + } +} + +var pieceSlotTemplate *template.Template + // Counter is POGO-like holder for data needed for other parts to fill and draw // a counter in a container type Counter struct { @@ -72,7 +81,7 @@ func (c *Counter) GetTextInPosition(i int) string { // filenumber: CounterTemplate.PositionNumberForFilename. So it will always be fixed number // position: The position of the text in the counter (0-16) // suffix: A suffix on the file. Constant -func (c *Counter) GetCounterFilename(position int, filenamesInUse *sync.Map) string { +func (c *Counter) GetCounterFilename(sideName string, position int, filenamesInUse *sync.Map) string { if c.Filename != "" { return c.Filename } @@ -91,7 +100,6 @@ func (c *Counter) GetCounterFilename(position int, filenamesInUse *sync.Map) str // This way, the positional based name will always be the first part of the filename // while the manual title will come later. This is useful when using prototypes so that // counters with the same positional name are close together in the destination folder - // by "formation" (belonging) instead of by "use" (title) name = "" if c.Extra.Side != "" { @@ -132,6 +140,12 @@ func (c *Counter) GetCounterFilename(position int, filenamesInUse *sync.Map) str res = strings.TrimSpace(res) filenamesInUse.Store(res, true) + if c.Extra == nil { + c.Extra = &Extra{} + } + if sideName != "" { + res = sideName + "_" + res + } c.Extra.Title = res res += ".png" @@ -230,35 +244,23 @@ func (c *Counter) ToVassal(sideName string) error { return nil } - pieceTemplate := "+/null/prototype;Basic Pieces emb2;" + - "{{ .FlipName }};128;A;;128;;;128;;;;1;false;0;0;" + - "{{ .BackFilename }};Back;true;Flip;;;false;;1;1;false;;;;Description;1.0;;true\\ piece;;;" + - "{{ .FrontFilename }};" + - "{{ .PieceName }}/ -1\\ null;0;0;;1;ppScale;1.0" - - xmlTemplate, err := template.New("xml").Parse(pieceTemplate) - if err != nil { - return fmt.Errorf("could not parse template string %w", err) - } - - uuid := uuid.New().String() buf := bytes.NewBufferString("") pieceTemp := PieceTemplateData{ FrontFilename: c.Filename, BackFilename: c.Extra.Title + "_back.png", FlipName: sideName, PieceName: c.Filename, - Id: uuid, + Id: c.Extra.Title, } - err = xmlTemplate.ExecuteTemplate(buf, "xml", pieceTemp) + err := pieceSlotTemplate.ExecuteTemplate(buf, "piece_text", pieceTemp) if err != nil { return fmt.Errorf("could not execute template %w", err) } piece := PieceSlot{ EntryName: c.Filename, - Gpid: uuid, + Gpid: c.Extra.Title, Height: c.Height, Width: c.Width, Data: buf.String(), diff --git a/counter_prototype.go b/counter_prototype.go index b00aa13..f994260 100644 --- a/counter_prototype.go +++ b/counter_prototype.go @@ -52,9 +52,9 @@ func (p *CounterPrototype) ToCounters(filenamesInUse *sync.Map, sideName string, var counterFilename string if p.Extra != nil && p.Extra.TitlePosition != nil { - counterFilename = newCounter.GetCounterFilename(*p.Extra.TitlePosition, filenamesInUse) + counterFilename = newCounter.GetCounterFilename(sideName, *p.Extra.TitlePosition, filenamesInUse) } else { - counterFilename = newCounter.GetCounterFilename(positionNumberForFilename, filenamesInUse) + counterFilename = newCounter.GetCounterFilename(sideName, positionNumberForFilename, filenamesInUse) } newCounter.Filename = counterFilename @@ -74,7 +74,7 @@ func (p *CounterPrototype) ToCounters(filenamesInUse *sync.Map, sideName string, if sideName != "" { err = backCounter.ToVassal(sideName) if err != nil { - log.Warn("could not create vassal piece") + log.Warn("could not create vassal piece", err) } } @@ -84,9 +84,10 @@ func (p *CounterPrototype) ToCounters(filenamesInUse *sync.Map, sideName string, if sideName != "" { err = newCounter.ToVassal(sideName) if err != nil { - log.Warn("could not create vassal piece") + log.Warn("could not create vassal piece", err) } } + cts = append(cts, newCounter) } diff --git a/counter_template.go b/counter_template.go index cf44a53..f94f0f1 100644 --- a/counter_template.go +++ b/counter_template.go @@ -2,7 +2,6 @@ package counters import ( "encoding/json" - "os" "sort" "sync" @@ -33,6 +32,13 @@ type CounterTemplate struct { Prototypes map[string]CounterPrototype `json:"prototypes,omitempty"` } +type VassalCounterTemplateSettings struct { + SideName string `json:"side_name,omitempty"` + ModuleName string `json:"module_name,omitempty"` + MapFile string `json:"map_file,omitempty"` + HexGrid *HexGrid `json:"hex_grid,omitempty"` +} + // ParseCounterTemplate reads a JSON file and parses it into a CounterTemplate after applying it some default settings (if not // present in the file) func ParseCounterTemplate(byt []byte, filenamesInUse *sync.Map) (t *CounterTemplate, err error) { @@ -52,14 +58,6 @@ func ParseCounterTemplate(byt []byte, filenamesInUse *sync.Map) (t *CounterTempl t.ApplyCounterWaterfallSettings() - // Request body contains the current working directory to use - // This is relevant because we need to use relavite paths - if t.WorkingDirectory != "" { - if err = os.Chdir(os.ExpandEnv(t.WorkingDirectory)); err != nil { - return nil, err - } - } - return } @@ -131,7 +129,7 @@ func (t *CounterTemplate) ParsePrototype() (*CounterTemplate, error) { // JSON counters to Counters newTemplate, err := t.ExpandPrototypeCounterTemplate(filenamesInUse) if err != nil { - return nil, errors.Wrap(err, "error trying to convert a counter template into another counter template") + return nil, errors.Wrap(err, "error trying to expand prototype template") } byt, err := json.Marshal(newTemplate) @@ -174,7 +172,20 @@ func (t *CounterTemplate) ExpandPrototypeCounterTemplate(filenamesInUse *sync.Ma } t.Prototypes = nil - return t, nil + } + + if t.Counters != nil { + for i, counter := range t.Counters { + if counter.Filename == "" { + var counterFilename string + if counter.Extra != nil && counter.Extra.TitlePosition != nil { + counterFilename = counter.GetCounterFilename(t.Vassal.SideName, *counter.Extra.TitlePosition, filenamesInUse) + } else { + counterFilename = counter.GetCounterFilename(t.Vassal.SideName, t.PositionNumberForFilename, filenamesInUse) + } + t.Counters[i].Filename = counterFilename + } + } } return t, nil diff --git a/counter_test.go b/counter_test.go index 728558d..bc15ad6 100644 --- a/counter_test.go +++ b/counter_test.go @@ -35,7 +35,7 @@ func TestGetCounterFilename(t *testing.T) { counter: Counter{ Texts: []Text{ {Settings: Settings{Position: 0}, String: "Test"}}, - Extra: &Extra{Title: stringP("ExtraTitle")}, + Extra: &Extra{Title: "ExtraTitle"}, }, position: 0, suffix: "suffix", @@ -75,7 +75,7 @@ func TestGetCounterFilename(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.counter.GetCounterFilename(tt.position, tt.suffix, tt.filenumber, tt.filenamesInUse) + got := tt.counter.GetCounterFilename("side", tt.position, tt.filenamesInUse) if got != tt.expected { t.Errorf("GetCounterFilename() = '%v', want '%v'", got, tt.expected) } diff --git a/fsops/file.go b/fsops/file.go index 1d2a38a..d8f0809 100644 --- a/fsops/file.go +++ b/fsops/file.go @@ -4,12 +4,13 @@ import ( "encoding/json" "encoding/xml" "fmt" + "io" "os" "path/filepath" "strings" + "github.com/charmbracelet/log" "github.com/pkg/errors" - "github.com/thehivecorporation/log" ) func ReadMarkupFile(markupFilepath string, destination interface{}) error { @@ -39,7 +40,7 @@ func ReadMarkupFile(markupFilepath string, destination interface{}) error { func FilenameExistsInFolder(filename, folder string) bool { fs, err := os.ReadDir(folder) if err != nil { - log.WithError(err).Fatal("could not read images folder") + log.Fatal("could not read images folder", "error", err) } for _, file := range fs { @@ -83,3 +84,31 @@ func GetFilenamesForPath(path string) ([]string, error) { return images, nil } + +// CopyFile copies a file from src to dst. If dst does not exist, it will be created. +func CopyFile(src, dst string) error { + fullpath, err := filepath.Abs(src) + if err != nil { + return err + } + + sourceFile, err := os.Open(fullpath) + if err != nil { + log.Error("Copying file", "src", fullpath, "dst", dst) + return err + } + defer sourceFile.Close() + + destinationFile, err := os.Create(dst) + if err != nil { + return err + } + defer destinationFile.Close() + + _, err = io.Copy(destinationFile, sourceFile) + if err != nil { + return err + } + + return destinationFile.Sync() +} diff --git a/output/counters_to_png.go b/output/counters_to_png.go index 343da4d..0abf82b 100644 --- a/output/counters_to_png.go +++ b/output/counters_to_png.go @@ -64,6 +64,24 @@ func newGlobalState(template *counters.CounterTemplate) *globalState { // CountersToPNG generates PNG images based on the provided CounterTemplate. func CountersToPNG(template *counters.CounterTemplate) { + // Request body contains the current working directory to use + // This is relevant because we need to use relavite paths + if template.WorkingDirectory != "" { + dir, err := os.Getwd() + if err != nil { + log.Error("error trying to get current working directory", err) + return + } + defer os.Chdir(dir) + + log.Info("Changing working directory", "chdir", os.ExpandEnv(template.WorkingDirectory)) + if err = os.Chdir(os.ExpandEnv(template.WorkingDirectory)); err != nil { + log.Error("error trying to change working directory", err) + return + } + } else { + log.Info("No working directory provided, using current working directory") + } _ = os.MkdirAll(template.OutputFolder, 0750) gs := newGlobalState(template) @@ -83,7 +101,7 @@ func CountersToPNG(template *counters.CounterTemplate) { defer pbar.Quit() ch := make(chan *counters.Counter, 50) - for i := 0; i < 50; i++ { + for i := 0; i < 100; i++ { go generateCounterToFile(ch, template.DrawGuides, gs, pbar, template.Vassal.SideName) } @@ -113,7 +131,7 @@ func generateCounterToFile(ch <-chan *counters.Counter, drawGuides bool, gs *glo iw := imageWriter{canvas: counterCanvas, template: gs.template} if err = iw.createFile(counter, gs); err != nil { - log.Error("error trying to write counter to file", err) + log.Error(err) pbar.Send(1) continue } @@ -139,7 +157,8 @@ func (iw *imageWriter) createFile(counter *counters.Counter, gs *globalState) er filepath := path.Join(iw.template.OutputFolder, counter.Filename) if err := iw.canvas.SavePNG(filepath); err != nil { - return fmt.Errorf("could not save PNG file: %w", err) + return fmt.Errorf("could not save PNG file '%s' (filename '%s'): %w", filepath, + counter.Filename, err) } gs.incrFilenumber() diff --git a/output/counters_to_vassal.go b/output/counters_to_vassal.go index a7eeb9f..8d6c4af 100644 --- a/output/counters_to_vassal.go +++ b/output/counters_to_vassal.go @@ -1,169 +1,133 @@ package output import ( - "bytes" "encoding/xml" "fmt" - "html/template" "os" - "sync" + "path" "github.com/pkg/errors" "github.com/sayden/counters" "github.com/sayden/counters/fsops" + "github.com/sayden/counters/input" + "github.com/sayden/counters/vassal" ) -// GetVassalDataForCounters returns the Vassal module data for the counters -func GetVassalDataForCounters(t *counters.CounterTemplate, xmlFilepath string) ([]byte, error) { - var g counters.VassalGameModule - err := fsops.ReadMarkupFile(xmlFilepath, &g) +func VassalModule(outputPath string, templatesFiles []string) error { + os.MkdirAll(outputPath, 0755) + destDir, err := os.MkdirTemp(outputPath, "vassal") if err != nil { - return nil, errors.Wrap(err, "error trying to decode content") + return errors.Wrap(err, "error creating temporary directory") } + defer os.RemoveAll(destDir) - // Piece palette definition - tw := counters.TabWidget{ - EntryName: "Forces", - ListWidget: make([]counters.ListWidget, 0), - } - - forces := make(map[string]counters.ListWidget) - forces["Markers"] = counters.ListWidget{ - EntryName: "Markers", - PieceSlot: make([]counters.PieceSlot, 0), - Scale: "1.0", - Height: "215", - Width: "562", - Divider: "194", - } + os.MkdirAll(path.Join(destDir, "images"), 0755) - // originalPieceTemplate := `+/null/prototype;Basic Pieces emb2;Flip1;128;A;;128;;;128;;;;1;false;0;0;1 TD X HQ.png;Back;true;Flip Layer (Name);;;false;;1;1;false;;;;Description;1.0;;true\ piece;;;1 TD Unit.png;1 TD Unit/ -1\ null;0;0;;1;ppScale;1.0` + listOfListWidgets := make([]counters.ListWidget, 0, 3) - xmlTemplateString := `+/null/prototype;BasicPrototype piece;;;{{ .Filename }};{{ .PieceName}}/ null;0;0;{{ .Id }};0` - xmlTemplate, err := template.New("xml").Parse(xmlTemplateString) - if err != nil { - return nil, errors.Wrap(err, "could not parse template string") + moduleName := "" + mapFilename := "" + hexGrid := counters.HexGrid{ + Color: "204,0,204", + CornersLegal: "false", + DotsVisible: "false", + EdgesLegal: "false", + SnapTo: "true", + Visible: "false", } - // Read special files from images folder to statically load them into the module as pieces - // (terrain, -1, -2 markers, Disorganized, Spent, OOS, etc,) - files, err := readFiles(counters.BASE_FOLDER + "/images") - if err != nil { - return nil, errors.Wrap(err, "could not read files") - } + // create all the counters in their respective folders + for _, inputPath := range templatesFiles { + if err := counters.ValidateSchemaAtPath[counters.CounterTemplate](inputPath); err != nil { + return errors.Wrap(err, "schema validation failed during jsonToAsset") + } - gpid := 200 - id := 200 - - // Load markers into the module - for _, file := range files { - buf := bytes.NewBufferString("") - err = xmlTemplate.ExecuteTemplate(buf, "xml", counters.TemplateData{ - Filename: file, - PieceName: file, - Id: fmt.Sprintf("%d", id), - }) + counterTemplate, err := input.ReadCounterTemplate(inputPath) if err != nil { - return nil, errors.Wrap(err, "error trying to write Vassal xml file using templates") + return errors.Wrap(err, "error reading counter template") } - id++ - - piece := counters.PieceSlot{ - EntryName: file, - Gpid: fmt.Sprintf("%d", gpid), - Height: t.Height, - Width: t.Width, - Data: buf.String(), + + newTemplate, err := counterTemplate.ParsePrototype() + if err != nil { + return errors.Wrap(err, "error parsing prototyped template") } - temp := forces["Markers"] - temp.PieceSlot = append(forces["Markers"].PieceSlot, piece) - forces["Markers"] = temp + newTemplate.OutputFolder = path.Join(destDir, "images") + moduleName = newTemplate.Vassal.ModuleName + mapFilename = newTemplate.Vassal.MapFile + + if newTemplate.Vassal.HexGrid != nil { + hexGrid = *newTemplate.Vassal.HexGrid + hexGrid.Color = "204,0,204" + hexGrid.CornersLegal = "false" + hexGrid.DotsVisible = "false" + hexGrid.EdgesLegal = "false" + hexGrid.SnapTo = "true" + hexGrid.Visible = "false" + } - gpid++ - } + CountersToPNG(newTemplate) - filenamesInUse := new(sync.Map) - - // Load counters into the module - for _, counter := range t.Counters { - buf := bytes.NewBufferString("") - if err = xmlTemplate.ExecuteTemplate(buf, "xml", - counters.TemplateData{ - Filename: counter.GetCounterFilename(t.PositionNumberForFilename, filenamesInUse), - PieceName: counter.GetCounterFilename(t.PositionNumberForFilename, filenamesInUse), - Id: fmt.Sprintf("%d", id), - }, - ); err != nil { - return nil, errors.Wrap(err, "error trying to write Vassal xml file using templates") - } - id++ - - piece := counters.PieceSlot{ - EntryName: counter.GetTextInPosition(t.PositionNumberForFilename), - Gpid: fmt.Sprintf("%d", gpid), - Height: t.Height, - Width: t.Width, - Data: buf.String(), + // get an array of the vassal pieces + list := counters.ListWidget{ + EntryName: newTemplate.Vassal.SideName, + PieceSlot: make([]counters.PieceSlot, 0, len(newTemplate.Counters)), + Scale: "1.0", + Height: "215", + Width: "562", + Divider: "194", } - if _, ok := forces[counter.Extra.Side]; !ok { - forces[counter.Extra.Side] = counters.ListWidget{ - EntryName: counter.Extra.Side, - PieceSlot: make([]counters.PieceSlot, 0), - Scale: "1.0", - Height: "215", - Width: "562", - Divider: "194", + for i, counter := range newTemplate.Counters { + if counter.VassalPiece != nil { + list.PieceSlot = append(list.PieceSlot, *newTemplate.Counters[i].VassalPiece) } } - temp := forces[counter.Extra.Side] - temp.PieceSlot = append(forces[counter.Extra.Side].PieceSlot, piece) - forces[counter.Extra.Side] = temp - - gpid++ + listOfListWidgets = append(listOfListWidgets, list) } - tw.ListWidget = append(tw.ListWidget, mapToArray[counters.ListWidget](forces)...) - g.PieceWindow.TabWidget = tw - - byt, err := xml.MarshalIndent(g, "", " ") - if err != nil { - return nil, errors.Wrap(err, "could not marshal the final game module data") + // Copy map file to the images folder + if err = fsops.CopyFile(mapFilename, path.Join(destDir, "images", path.Base(mapFilename))); err != nil { + return errors.Wrap(err, "error copying map file") } - return byt, nil + return writeXMLFiles(destDir, moduleName, mapFilename, listOfListWidgets, outputPath, &hexGrid) } -func readFiles(path string) ([]string, error) { - files, err := os.ReadDir(path) - if err != nil { - return nil, err - } - - filenames := make([]string, 0) +func writeXMLFiles(dir, moduleName, mapFilename string, listOfWidgets []counters.ListWidget, outputPath string, hexGrid *counters.HexGrid) error { + // buildFile.xml + buildFile := vassal.GetBuildFile() - for _, file := range files { - if !file.IsDir() { - if file.Name()[0] == '_' { - filenames = append(filenames, file.Name()) - } - } + buildFile.Name = moduleName + buildFile.Map.BoardPicker.Board.Image = path.Base(mapFilename) + buildFile.Map.BoardPicker.Board.Name = moduleName + if hexGrid != nil { + buildFile.Map.BoardPicker.Board.HexGrid = *hexGrid } + buildFile.PieceWindow.TabWidget.ListWidget = listOfWidgets - return filenames, nil -} - -func mapToArray[T any](m map[string]T) []T { - temp := make([]T, len(m)) + f, err := os.Create(path.Join(dir, "buildFile.xml")) + if err != nil { + return fmt.Errorf("error creating buildFile.xml: %w", err) + } + defer f.Close() - i := 0 - for _, item := range m { - temp[i] = item + err = xml.NewEncoder(f).Encode(buildFile) + if err != nil { + return fmt.Errorf("error encoding buildFile.xml: %w", err) + } - i++ + // moduledata + moduleData := vassal.GetModuleData() + moduleData.Name = moduleName + f2, err := os.Create(path.Join(dir, "moduledata")) + if err != nil { + return fmt.Errorf("error creating buildFile.xml: %w", err) } + defer f2.Close() + xml.NewEncoder(f2).Encode(moduleData) - return temp + // Compress + return WriteZipFileWithFolderContent(path.Join("/tmp/test", moduleName+".vmod"), dir) } diff --git a/output/csv_to_vassal.go b/output/csv_to_vassal.go new file mode 100644 index 0000000..714c466 --- /dev/null +++ b/output/csv_to_vassal.go @@ -0,0 +1,222 @@ +package output + +import ( + "bytes" + "encoding/xml" + "fmt" + "html/template" + "os" + "path" + "sync" + + "github.com/pkg/errors" + "github.com/sayden/counters" + "github.com/sayden/counters/fsops" + "github.com/sayden/counters/input" + "github.com/thehivecorporation/log" +) + +type VassalConfig struct { + Csv string `help:"Input path of the file to read. Be aware that some outputs requires specific inputs."` + VassalOutputFile string `help:"Name and path of .vmod file to write. The extension .vmod is required"` + CounterTitle int `help:"The title for the counter and the file with the image comes from a column in the CSV file. Define which column here, 0 indexed" default:"3"` +} + +// CSVToVassalFile takes a CSV as an input and creates a Vassal module file as output +// It uses the Vassal module stored in the TemplateModule folder as a Vassal prototype to build over it +func CSVToVassalFile(cfg VassalConfig) error { + // Ensure that the extension of the output file is Vmod + if path.Ext(cfg.VassalOutputFile) != "vmod" { + log.Fatal("output file path for vassal must have '.vmod' extension") + } + + var counterTemplate *counters.CounterTemplate + var err error + counterTemplate, err = input.ReadCounterTemplate(cfg.Csv, cfg.VassalOutputFile) + if err != nil { + return err + } + + // Vassal mode forces individual rendering of counters + counterTemplate.Mode = counters.TEMPLATE_MODE_TEMPLATE + counterTemplate.OutputFolder = counters.BASE_FOLDER + "/images" + counterTemplate.PositionNumberForFilename = 3 + + CountersToPNG(counterTemplate) + + // Hardcoded output file, user selects the output of the vmod file + xmlBytes, err := getVassalDataForCounters(counterTemplate, counters.VassalInputXmlFile) + if err != nil { + log.WithError(err).Fatal("could not create xml file for vassal") + } + + if err = os.WriteFile(counters.VassalOutputXmlFile, xmlBytes, 0666); err != nil { + log.WithError(err).Fatal("no output xml file was generated") + } + + return WriteZipFileWithFolderContent(cfg.VassalOutputFile, counters.BASE_FOLDER) +} + +// getVassalDataForCounters returns the Vassal module data for the counters +func getVassalDataForCounters(t *counters.CounterTemplate, xmlFilepath string) ([]byte, error) { + var g counters.VassalGameModule + err := fsops.ReadMarkupFile(xmlFilepath, &g) + if err != nil { + return nil, errors.Wrap(err, "error trying to decode content") + } + + // Piece palette definition + tw := counters.TabWidget{ + EntryName: "Forces", + // PanelWidget: counters.PanelWidget{ + // ListWidget: make([]counters.ListWidget, 0), + // EntryName: "Forces", + // NColumns: "3", + // Scale: "1.0", + // Text: "Forces", + // Vert: "false", + // Fixed: "false", + // }, + } + + forces := make(map[string]counters.ListWidget) + forces["Markers"] = counters.ListWidget{ + EntryName: "Markers", + PieceSlot: make([]counters.PieceSlot, 0), + Scale: "1.0", + Height: "215", + Width: "562", + Divider: "194", + } + + // originalPieceTemplate := `+/null/prototype;Basic Pieces emb2;Flip1;128;A;;128;;;128;;;;1;false;0;0;1 TD X HQ.png;Back;true;Flip Layer (Name);;;false;;1;1;false;;;;Description;1.0;;true\ piece;;;1 TD Unit.png;1 TD Unit/ -1\ null;0;0;;1;ppScale;1.0` + + xmlTemplateString := `+/null/prototype;BasicPrototype piece;;;{{ .Filename }};{{ .PieceName}}/ null;0;0;{{ .Id }};0` + xmlTemplate, err := template.New("xml").Parse(xmlTemplateString) + if err != nil { + return nil, errors.Wrap(err, "could not parse template string") + } + + // Read special files from images folder to statically load them into the module as pieces + // (terrain, -1, -2 markers, Disorganized, Spent, OOS, etc,) + files, err := readFiles(counters.BASE_FOLDER + "/images") + if err != nil { + return nil, errors.Wrap(err, "could not read files") + } + + gpid := 200 + id := 200 + + // Load markers into the module + for _, file := range files { + buf := bytes.NewBufferString("") + err = xmlTemplate.ExecuteTemplate(buf, "xml", counters.CSVTemplateData{ + Filename: file, + PieceName: file, + Id: fmt.Sprintf("%d", id), + }) + if err != nil { + return nil, errors.Wrap(err, "error trying to write Vassal xml file using templates") + } + id++ + + piece := counters.PieceSlot{ + EntryName: file, + Gpid: fmt.Sprintf("%d", gpid), + Height: t.Height, + Width: t.Width, + Data: buf.String(), + } + + temp := forces["Markers"] + temp.PieceSlot = append(forces["Markers"].PieceSlot, piece) + forces["Markers"] = temp + + gpid++ + } + + filenamesInUse := new(sync.Map) + + // Load counters into the module + for _, counter := range t.Counters { + buf := bytes.NewBufferString("") + if err = xmlTemplate.ExecuteTemplate(buf, "xml", + counters.CSVTemplateData{ + Filename: counter.GetCounterFilename("", t.PositionNumberForFilename, filenamesInUse), + PieceName: counter.GetCounterFilename("", t.PositionNumberForFilename, filenamesInUse), + Id: fmt.Sprintf("%d", id), + }, + ); err != nil { + return nil, errors.Wrap(err, "error trying to write Vassal xml file using templates") + } + id++ + + piece := counters.PieceSlot{ + EntryName: counter.GetTextInPosition(t.PositionNumberForFilename), + Gpid: fmt.Sprintf("%d", gpid), + Height: t.Height, + Width: t.Width, + Data: buf.String(), + } + + if _, ok := forces[counter.Extra.Side]; !ok { + forces[counter.Extra.Side] = counters.ListWidget{ + EntryName: counter.Extra.Side, + PieceSlot: make([]counters.PieceSlot, 0), + Scale: "1.0", + Height: "215", + Width: "562", + Divider: "194", + } + } + + temp := forces[counter.Extra.Side] + temp.PieceSlot = append(forces[counter.Extra.Side].PieceSlot, piece) + forces[counter.Extra.Side] = temp + + gpid++ + } + + // tw.PanelWidget.ListWidget = append(tw.PanelWidget.ListWidget, mapToArray[counters.ListWidget](forces)...) + tw.ListWidget = append(tw.ListWidget, mapToArray[counters.ListWidget](forces)...) + g.PieceWindow.TabWidget = tw + + byt, err := xml.MarshalIndent(g, "", " ") + if err != nil { + return nil, errors.Wrap(err, "could not marshal the final game module data") + } + + return byt, nil +} + +func readFiles(path string) ([]string, error) { + files, err := os.ReadDir(path) + if err != nil { + return nil, err + } + + filenames := make([]string, 0) + + for _, file := range files { + if !file.IsDir() { + if file.Name()[0] == '_' { + filenames = append(filenames, file.Name()) + } + } + } + + return filenames, nil +} + +func mapToArray[T any](m map[string]T) []T { + temp := make([]T, len(m)) + + i := 0 + for _, item := range m { + temp[i] = item + + i++ + } + + return temp +} diff --git a/output/zip.go b/output/zip.go index fc98ab6..ebbcb7f 100644 --- a/output/zip.go +++ b/output/zip.go @@ -3,25 +3,28 @@ package output import ( "archive/zip" "fmt" - "github.com/thehivecorporation/log" "os" + + "github.com/charmbracelet/log" ) -func WriteZipFileWithFolderContent(destinationZipFilepath, inputFolder string) error { +func WriteZipFileWithFolderContent(destinationZipfilePath, inputFolder string) error { // Create the zip/vmod file - outFile, err := os.Create(destinationZipFilepath) + outFile, err := os.Create(destinationZipfilePath) if err != nil { - log.WithError(err).Fatal("could not create destination vassal file") + log.Fatal("could not create destination vassal file", "error", err) } defer outFile.Close() z := zip.NewWriter(outFile) defer func() { if err = z.Close(); err != nil { - log.WithError(err).Error("zip file had a problem when closing") + log.Error("zip file had a problem when closing", "error", err) } }() + log.Info("Using", "basepath", inputFolder, "dest_file", destinationZipfilePath) + return addFiles(z, inputFolder, "") } diff --git a/pipelines/csv_to_vassal.go b/pipelines/csv_to_vassal.go deleted file mode 100644 index e1f5502..0000000 --- a/pipelines/csv_to_vassal.go +++ /dev/null @@ -1,52 +0,0 @@ -package pipelines - -import ( - "os" - "path" - - "github.com/sayden/counters" - "github.com/sayden/counters/input" - "github.com/sayden/counters/output" - "github.com/thehivecorporation/log" -) - -type VassalConfig struct { - Csv string `help:"Input path of the file to read. Be aware that some outputs requires specific inputs." required:"true"` - VassalOutputFile string `help:"Name and path of .vmod file to write. The extension .vmod is required" required:"true"` - CounterTitle int `help:"The title for the counter and the file with the image comes from a column in the CSV file. Define which column here, 0 indexed" default:"3"` -} - -// CSVToVassalFile takes a CSV as an input and creates a Vassal module file as output -// It uses the Vassal module stored in the TemplateModule folder as a Vassal prototype to build over it -func CSVToVassalFile(cfg VassalConfig) error { - // Ensure that the extension of the output file is Vmod - if path.Ext(cfg.VassalOutputFile) != "vmod" { - log.Fatal("output file path for vassal must have '.vmod' extension") - } - - var counterTemplate *counters.CounterTemplate - var err error - counterTemplate, err = input.ReadCounterTemplate(cfg.Csv, cfg.VassalOutputFile) - if err != nil { - return err - } - - // Vassal mode forces individual rendering of counters - counterTemplate.Mode = counters.TEMPLATE_MODE_TEMPLATE - counterTemplate.OutputFolder = counters.BASE_FOLDER + "/images" - counterTemplate.PositionNumberForFilename = 3 - - output.CountersToPNG(counterTemplate) - - // Hardcoded output file, user selects the output of the vmod file - xmlBytes, err := output.GetVassalDataForCounters(counterTemplate, counters.VassalInputXmlFile) - if err != nil { - log.WithError(err).Fatal("could not create xml file for vassal") - } - - if err = os.WriteFile(counters.VassalOutputXmlFile, xmlBytes, 0666); err != nil { - log.WithError(err).Fatal("no output xml file was generated") - } - - return output.WriteZipFileWithFolderContent(cfg.VassalOutputFile, counters.BASE_FOLDER) -} diff --git a/server/main.go b/server/main.go index 4d19d90..f71e4bf 100644 --- a/server/main.go +++ b/server/main.go @@ -156,6 +156,9 @@ func generateCounter(byt []byte) (response, error) { if err != nil { return nil, err } + cwd, _ := os.Getwd() + defer os.Chdir(cwd) + os.Chdir(os.ExpandEnv(tempTemplate.WorkingDirectory)) newTemplate, err := tempTemplate.ParsePrototype() if err != nil { @@ -178,7 +181,7 @@ func generateCounter(byt []byte) (response, error) { counterImage := counterImage{ CounterImage: "data:image/png;base64," + buf.String(), - Id: counter.GetCounterFilename(i, filenamesInUse), + Id: counter.GetCounterFilename("", i, filenamesInUse), } i++ diff --git a/vassal.go b/vassal.go index 75b9602..06d754d 100644 --- a/vassal.go +++ b/vassal.go @@ -3,8 +3,10 @@ package counters import "encoding/xml" const ( - counterTemplate = `+/null/prototype;UnitStep prototype;RU\ emb2;Activate;128;A;;128;;;128;;;;1;false;0;0;{{ .FilenameFront }},{{ .FilenameBack }};,;false;{{ .CounterName }};;;true;StepValue;1;1;true;65,130;;;;1.0;;true\\ piece;;;{{ .FilenameFront }};{{ .Id }}/ \ 1\\ null;0;0;398;0` - oldTemplate = `+/null/prototype;BasicPrototype piece;;;{{ .Filename }};{{ .PieceName}}/ null;0;0;{{ .Id }};0` + Template_VassalPiece = `+/null/prototype;UnitStep prototype;RU\ emb2;Activate;128;A;;128;;;128;;;;1;false;0;0;{{ .FrontFilename }},{{ .BackFilename }};,;false;{{ .PieceName }};;;true;StepValue;1;1;true;65,130;;;;1.0;;true\\ piece;;;{{ .FrontFilename }};{{ .Id }}/ \ 1\\ null;0;0;398;0` + Template_NewVassalPiece = `+/null/prototype;Prototype emb2;Next;128;A;;128;;;128;;;;1;false;0;0;{{.BackFilename }};;true;{{.FrontFilename}};;;false;StepValue;1;1;false;65,130;;;;1.0;;true\ piece;;;{{.FrontFilename}};{{.PieceName}}/ -1\ null;117;107;89;1;ppScale;1.0` + Template_Reference_VassalPiece = `+/null/prototype;UnitStep prototype;RU\ emb2;Activate;128;A;;128;;;128;;;;1;false;0;0;{{ .FilenameFront }},{{ .FilenameBack }};,;false;{{ .CounterName }};;;true;StepValue;1;1;true;65,130;;;;1.0;;true\\ piece;;;{{ .FilenameFront }};{{ .Id }}/ \ 1\\ null;0;0;398;0` + Template_OldPiece = `+/null/prototype;BasicPrototype piece;;;{{ .Filename }};{{ .PieceName}}/ null;0;0;{{ .Id }};0` ) type VassalGameModule struct { @@ -17,27 +19,191 @@ type VassalGameModule struct { Name string `xml:"name,attr"` NextPieceSlotId string `xml:"nextPieceSlotId,attr"` Version string `xml:"version,attr"` - BasicCommandEncoder Capture `xml:"VASSAL.build.module.BasicCommandEncoder"` - Documentation Capture `xml:"VASSAL.build.module.Documentation"` - Chatter Capture `xml:"VASSAL.build.module.Chatter"` - KeyNamer Capture `xml:"VASSAL.build.module.KeyNamer"` + BasicCommandEncoder capture `xml:"VASSAL.build.module.BasicCommandEncoder"` + Documentation capture `xml:"VASSAL.build.module.Documentation"` + Chatter capture `xml:"VASSAL.build.module.Chatter"` + KeyNamer capture `xml:"VASSAL.build.module.KeyNamer"` PieceWindow PieceWindow DiceButton []DiceButton `xml:"VASSAL.build.module.DiceButton"` - PlayerRoster Capture `xml:"VASSAL.build.module.PlayerRoster"` - GlobalOptions Capture `xml:"VASSAL.build.module.GlobalOptions"` - GamePieceDefinitions Capture `xml:"VASSAL.build.module.gamepieceimage.GamePieceImageDefinitions"` - GlobalProperties Capture `xml:"VASSAL.build.module.properties.GlobalProperties"` - GlobalTranslatableMessages Capture `xml:"VASSAL.build.module.properties.GlobalTranslatableMessages"` - PrototypesContainer Capture `xml:"VASSAL.build.module.PrototypesContainer"` - Language Capture `xml:"VASSAL.i18n.Language"` - Map Capture `xml:"VASSAL.build.module.Map"` + PlayerRoster capture `xml:"VASSAL.build.module.PlayerRoster"` + GlobalOptions capture `xml:"VASSAL.build.module.GlobalOptions"` + GamePieceDefinitions capture `xml:"VASSAL.build.module.gamepieceimage.GamePieceImageDefinitions"` + GlobalProperties capture `xml:"VASSAL.build.module.properties.GlobalProperties"` + GlobalTranslatableMessages capture `xml:"VASSAL.build.module.properties.GlobalTranslatableMessages"` + PrototypesContainer capture `xml:"VASSAL.build.module.PrototypesContainer"` + Language capture `xml:"VASSAL.i18n.Language"` + Map Map `xml:"VASSAL.build.module.Map"` } -type Capture struct { +type Map struct { + XMLName xml.Name `xml:"VASSAL.build.module.Map"` + + BoardPicker BoardPicker `xml:"VASSAL.build.module.map.BoardPicker"` + Text string `xml:",chardata"` + AllowMultiple string `xml:"allowMultiple,attr"` + Backgroundcolor string `xml:"backgroundcolor,attr"` + ButtonName string `xml:"buttonName,attr"` + ChangeFormat string `xml:"changeFormat,attr"` + Color string `xml:"color,attr"` + CreateFormat string `xml:"createFormat,attr"` + EdgeHeight string `xml:"edgeHeight,attr"` + EdgeWidth string `xml:"edgeWidth,attr"` + HideKey string `xml:"hideKey,attr"` + Hotkey string `xml:"hotkey,attr"` + Icon string `xml:"icon,attr"` + Launch string `xml:"launch,attr"` + MapName string `xml:"mapName,attr"` + MarkMoved string `xml:"markMoved,attr"` + MarkUnmovedHotkey string `xml:"markUnmovedHotkey,attr"` + MarkUnmovedIcon string `xml:"markUnmovedIcon,attr"` + MarkUnmovedReport string `xml:"markUnmovedReport,attr"` + MarkUnmovedText string `xml:"markUnmovedText,attr"` + MarkUnmovedTooltip string `xml:"markUnmovedTooltip,attr"` + MoveKey string `xml:"moveKey,attr"` + MoveToFormat string `xml:"moveToFormat,attr"` + MoveWithinFormat string `xml:"moveWithinFormat,attr"` + OnlyReportChangedLocation string `xml:"onlyReportChangedLocation,attr"` + ShowKey string `xml:"showKey,attr"` + Thickness string `xml:"thickness,attr"` + StackMetrics struct { + Text string `xml:",chardata"` + Bottom string `xml:"bottom,attr"` + Disabled string `xml:"disabled,attr"` + Down string `xml:"down,attr"` + ExSepX string `xml:"exSepX,attr"` + ExSepY string `xml:"exSepY,attr"` + Top string `xml:"top,attr"` + UnexSepX string `xml:"unexSepX,attr"` + UnexSepY string `xml:"unexSepY,attr"` + Up string `xml:"up,attr"` + } `xml:"VASSAL.build.module.map.StackMetrics"` + ForwardToKeyBuffer string `xml:"VASSAL.build.module.map.ForwardToKeyBuffer"` + Scroller string `xml:"VASSAL.build.module.map.Scroller"` + ForwardToChatter string `xml:"VASSAL.build.module.map.ForwardToChatter"` + MenuDisplayer string `xml:"VASSAL.build.module.map.MenuDisplayer"` + MapCenterer string `xml:"VASSAL.build.module.map.MapCenterer"` + StackExpander string `xml:"VASSAL.build.module.map.StackExpander"` + PieceMover string `xml:"VASSAL.build.module.map.PieceMover"` + KeyBufferer string `xml:"VASSAL.build.module.map.KeyBufferer"` + ImageSaver struct { + Text string `xml:",chardata"` + ButtonText string `xml:"buttonText,attr"` + CanDisable string `xml:"canDisable,attr"` + DisabledIcon string `xml:"disabledIcon,attr"` + HideWhenDisabled string `xml:"hideWhenDisabled,attr"` + Hotkey string `xml:"hotkey,attr"` + Icon string `xml:"icon,attr"` + PropertyGate string `xml:"propertyGate,attr"` + Tooltip string `xml:"tooltip,attr"` + } `xml:"VASSAL.build.module.map.ImageSaver"` + CounterDetailViewer struct { + Text string `xml:",chardata"` + BgColor string `xml:"bgColor,attr"` + BorderColor string `xml:"borderColor,attr"` + BorderInnerThickness string `xml:"borderInnerThickness,attr"` + BorderThickness string `xml:"borderThickness,attr"` + BorderWidth string `xml:"borderWidth,attr"` + CenterAll string `xml:"centerAll,attr"` + CenterPiecesVertically string `xml:"centerPiecesVertically,attr"` + CenterText string `xml:"centerText,attr"` + CombineCounterSummary string `xml:"combineCounterSummary,attr"` + CounterReportFormat string `xml:"counterReportFormat,attr"` + Delay string `xml:"delay,attr"` + Description string `xml:"description,attr"` + Display string `xml:"display,attr"` + EmptyHexReportForma string `xml:"emptyHexReportForma,attr"` + EnableHTML string `xml:"enableHTML,attr"` + ExtraTextPadding string `xml:"extraTextPadding,attr"` + FgColor string `xml:"fgColor,attr"` + FontSize string `xml:"fontSize,attr"` + GraphicsZoom string `xml:"graphicsZoom,attr"` + Hotkey string `xml:"hotkey,attr"` + LayerList string `xml:"layerList,attr"` + MinDisplayPieces string `xml:"minDisplayPieces,attr"` + OnlyShowFirstSummary string `xml:"onlyShowFirstSummary,attr"` + PropertyFilter string `xml:"propertyFilter,attr"` + ShowDeck string `xml:"showDeck,attr"` + ShowDeckDepth string `xml:"showDeckDepth,attr"` + ShowDeckMasked string `xml:"showDeckMasked,attr"` + ShowMoveSelectde string `xml:"showMoveSelectde,attr"` + ShowNoStack string `xml:"showNoStack,attr"` + ShowNonMovable string `xml:"showNonMovable,attr"` + ShowOnlyTopOfStack string `xml:"showOnlyTopOfStack,attr"` + ShowOverlap string `xml:"showOverlap,attr"` + ShowTerrainBeneath string `xml:"showTerrainBeneath,attr"` + ShowTerrainHeight string `xml:"showTerrainHeight,attr"` + ShowTerrainSnappy string `xml:"showTerrainSnappy,attr"` + ShowTerrainText string `xml:"showTerrainText,attr"` + ShowTerrainWidth string `xml:"showTerrainWidth,attr"` + ShowTerrainZoom string `xml:"showTerrainZoom,attr"` + Showgraph string `xml:"showgraph,attr"` + Showgraphsingle string `xml:"showgraphsingle,attr"` + Showtext string `xml:"showtext,attr"` + Showtextsingle string `xml:"showtextsingle,attr"` + StopAfterShowing string `xml:"stopAfterShowing,attr"` + StretchWidthPieces string `xml:"stretchWidthPieces,attr"` + StretchWidthSummary string `xml:"stretchWidthSummary,attr"` + SummaryReportFormat string `xml:"summaryReportFormat,attr"` + UnrotatePieces string `xml:"unrotatePieces,attr"` + Version string `xml:"version,attr"` + VerticalBottomText string `xml:"verticalBottomText,attr"` + VerticalOffset string `xml:"verticalOffset,attr"` + VerticalTopText string `xml:"verticalTopText,attr"` + Zoomlevel string `xml:"zoomlevel,attr"` + } `xml:"VASSAL.build.module.map.CounterDetailViewer"` + Flare struct { + Text string `xml:",chardata"` + CircleColor string `xml:"circleColor,attr"` + CircleScale string `xml:"circleScale,attr"` + CircleSize string `xml:"circleSize,attr"` + FlareKey string `xml:"flareKey,attr"` + FlareName string `xml:"flareName,attr"` + FlarePulses string `xml:"flarePulses,attr"` + FlarePulsesPerSec string `xml:"flarePulsesPerSec,attr"` + ReportFormat string `xml:"reportFormat,attr"` + } `xml:"VASSAL.build.module.map.Flare"` + Zoomer struct { + Text string `xml:",chardata"` + InButtonText string `xml:"inButtonText,attr"` + InIconName string `xml:"inIconName,attr"` + InTooltip string `xml:"inTooltip,attr"` + OutButtonText string `xml:"outButtonText,attr"` + OutIconName string `xml:"outIconName,attr"` + OutTooltip string `xml:"outTooltip,attr"` + PickButtonText string `xml:"pickButtonText,attr"` + PickIconName string `xml:"pickIconName,attr"` + PickTooltip string `xml:"pickTooltip,attr"` + ZoomInKey string `xml:"zoomInKey,attr"` + ZoomLevels string `xml:"zoomLevels,attr"` + ZoomOutKey string `xml:"zoomOutKey,attr"` + ZoomPickKey string `xml:"zoomPickKey,attr"` + ZoomStart string `xml:"zoomStart,attr"` + } `xml:"VASSAL.build.module.map.Zoomer"` + VASSALBuildModulePropertiesGlobalProperties string `xml:"VASSAL.build.module.properties.GlobalProperties"` + VASSALBuildModuleMapSelectionHighlighters string `xml:"VASSAL.build.module.map.SelectionHighlighters"` + HighlightLastMoved struct { + Text string `xml:",chardata"` + Color string `xml:"color,attr"` + Enabled string `xml:"enabled,attr"` + Thickness string `xml:"thickness,attr"` + } `xml:"VASSAL.build.module.map.HighlightLastMoved"` + HidePiecesButton struct { + Text string `xml:",chardata"` + ButtonText string `xml:"buttonText,attr"` + HiddenIcon string `xml:"hiddenIcon,attr"` + Hotkey string `xml:"hotkey,attr"` + ShowingIcon string `xml:"showingIcon,attr"` + Tooltip string `xml:"tooltip,attr"` + } `xml:"VASSAL.build.module.map.HidePiecesButton"` +} + +type capture struct { Raw string `xml:",innerxml"` } type DiceButton struct { + XMLName xml.Name `xml:"VASSAL.build.module.DiceButton"` + Raw string `xml:",innerxml"` AddToTotal int `xml:"addToTotal,attr"` CanDisable bool `xml:"canDisable,attr"` @@ -80,7 +246,20 @@ type PieceWindow struct { type TabWidget struct { XMLName xml.Name `xml:"VASSAL.build.widget.TabWidget"` + EntryName string `xml:"entryName,attr"` + // PanelWidget PanelWidget `xml:"VASSAL.build.widget.PanelWidget"` + ListWidget []ListWidget `xml:"VASSAL.build.widget.ListWidget"` +} + +type PanelWidget struct { + XMLName xml.Name `xml:"VASSAL.build.widget.PanelWidget"` + + Text string `xml:",chardata"` EntryName string `xml:"entryName,attr"` + Fixed string `xml:"fixed,attr"` + NColumns string `xml:"nColumns,attr"` + Scale string `xml:"scale,attr"` + Vert string `xml:"vert,attr"` ListWidget []ListWidget `xml:"VASSAL.build.widget.ListWidget"` } @@ -105,7 +284,61 @@ type PieceSlot struct { Data string `xml:",chardata"` } -type TemplateData struct { +type VassalFileModuleData struct { + XMLName xml.Name `xml:"data"` + + Text string `xml:",chardata"` + AttrVersion string `xml:"version,attr"` + Version string `xml:"version"` + Extra1 string `xml:"extra1"` + Extra2 string `xml:"extra2"` + VassalVersion string `xml:"VassalVersion"` + DateSaved string `xml:"dateSaved"` + Description string `xml:"description"` + Name string `xml:"name"` +} + +type BoardPicker struct { + XMLName xml.Name `xml:"VASSAL.build.module.map.BoardPicker"` + Text string `xml:",chardata"` + AddColumnText string `xml:"addColumnText,attr"` + AddRowText string `xml:"addRowText,attr"` + BoardPrompt string `xml:"boardPrompt,attr"` + SlotHeight string `xml:"slotHeight,attr"` + SlotScale string `xml:"slotScale,attr"` + SlotWidth string `xml:"slotWidth,attr"` + Title string `xml:"title,attr"` + Board Board `xml:"VASSAL.build.module.map.boardPicker.Board"` +} + +type Board struct { + Text string `xml:",chardata"` + Image string `xml:"image,attr"` + Name string `xml:"name,attr"` + Reversible string `xml:"reversible,attr"` + HexGrid HexGrid `xml:"VASSAL.build.module.map.boardPicker.board.HexGrid"` +} + +type HexGrid struct { + XMLName xml.Name `xml:"VASSAL.build.module.map.boardPicker.board.HexGrid" json:"-"` + + Text string `xml:",chardata" json:"text,omitempty"` + Color string `xml:"color,attr" json:"color,omitempty"` + CornersLegal string `xml:"cornersLegal,attr" json:"cornersLegal,omitempty"` + DotsVisible string `xml:"dotsVisible,attr" json:"dotsVisible,omitempty"` + Dx string `xml:"dx,attr" json:"dx,omitempty"` + Dy string `xml:"dy,attr" json:"dy,omitempty"` + EdgesLegal string `xml:"edgesLegal,attr" json:"edgesLegal,omitempty"` + Sideways string `xml:"sideways,attr" json:"sideways,omitempty"` + SnapTo string `xml:"snapTo,attr" json:"snapTo,omitempty"` + Visible string `xml:"visible,attr" json:"visible,omitempty"` + X0 string `xml:"x0,attr" json:"x0,omitempty"` + Y0 string `xml:"y0,attr" json:"y0,omitempty"` +} + +// Templates + +type CSVTemplateData struct { Filename string PieceName string Id string @@ -118,7 +351,3 @@ type PieceTemplateData struct { Id string FlipName string } - -type VassalCounterTemplateSettings struct { - SideName string `json:"side_name,omitempty"` -} diff --git a/vassal/buildFile.xml b/vassal/buildFile.xml new file mode 100644 index 0000000..991fcef --- /dev/null +++ b/vassal/buildFile.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/null/delete;Delete;68,130; piece;;;;/ null;36;32;;0 + + + + diff --git a/vassal/buildFile_old.xml b/vassal/buildFile_old.xml new file mode 100644 index 0000000..35e310e --- /dev/null +++ b/vassal/buildFile_old.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/null/cmt; markmoved;moved.gif;0;0;Toggle whether piece marked as moved;77,130;;false;Mark piece as moved;;Mark piece as unmoved;\ rotate;6;93,130;91,130;Rotate CW;Rotate CCW;;;;;;;;true\\ AreaOfEffect;255,255,51;30;10;false;Show area1;57345,0,Show area2;;true;;Range;Range;Show area3;57347,0,Show area4;Show area5;57349,0,Show area6;true\\\ delete;Delete;68,130;\\\\ clone;Clone;67,130;\\\\\ piece;;;;/ false\ 0\\ false\\\ \\\\ \\\\\ null;36;32;;0 + + + + + diff --git a/vassal/embed.go b/vassal/embed.go new file mode 100644 index 0000000..c603216 --- /dev/null +++ b/vassal/embed.go @@ -0,0 +1,41 @@ +package vassal + +import ( + _ "embed" + "encoding/xml" + "fmt" + + "github.com/sayden/counters" +) + +//go:embed moduledata +var moduleDataBytes []byte +var moduleData counters.VassalFileModuleData + +//go:embed buildFile.xml +var buildFileBytes []byte +var buildFile counters.VassalGameModule + +type moduleTemplateData struct { + Name string +} + +func init() { + err := xml.Unmarshal(moduleDataBytes, &moduleData) + if err != nil { + panic(fmt.Errorf("error trying to decode content: %w", err)) + } + + err = xml.Unmarshal(buildFileBytes, &buildFile) + if err != nil { + panic(fmt.Errorf("error trying to decode content: %w", err)) + } +} + +func GetBuildFile() *counters.VassalGameModule { + return &buildFile +} + +func GetModuleData() *counters.VassalFileModuleData { + return &moduleData +} diff --git a/vassal/moduledata b/vassal/moduledata new file mode 100644 index 0000000..d71dc2a --- /dev/null +++ b/vassal/moduledata @@ -0,0 +1,10 @@ + + + 0.1 + + + 3.7.12 + 1730245172539 + + {{ .Name }} +