Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • PantyNabber/fc-pregmod
  • pregmodfan/fc-pregmod
  • Alice.Grant/fc-pregmod
  • powerfful/fc-pregmod
  • elstumpo/fc-pregmod
  • Y/fc-pregmod
  • floer/fc-pregmod
  • oidocrop/fc-pregmod
  • hawk5005/fc-pregmod
  • nn/fc-pregmod
  • latios298/fc-pregmod
  • brpregmodfan/fc-pregmod
  • SomeoneTookMyUserName/fc-pregmod
  • 000-250-006/fc-pregmod
  • rewrica/fc-pregmod
  • Stuffedgame/fc-pregmod
  • wisepants314/fc-pregmod
  • fcanon/fc-pregmod
  • randomanon1/pregmod-mod-cyberfurry
  • teddy.buckland/fc-pregmod
  • farsinian_591b7a2d8b49d/fc-pregmod
  • FCShotadev/fc-pregmod
  • uselessartifact/fc-pregmod
  • irina_90/fc-pregmod
  • thaumx/fc-pregmod
  • MouseOfLight/fc-pregmod
  • empresssela/fc-pregmod
  • MasterAaran/fc-pregmod
  • ishy/fc-pregmod
  • psychofox/fc-pregmod
  • shadomancer/fc-pregmod
  • tycrakgg/fc-pregmod
  • azktaawc/fc-pregmod
  • andonno07/fc-pregmod
  • Onithyr/fc-pregmod
  • redneck987.jl/fc-pregmod
  • Farseeker/fc-pregmod
  • milliarc/fc-pregmod
  • BeefimusMaximus/fc-pregmod
  • magicknight79/fc-pregmod
  • hexall90/fc-pregmod
  • cantworkforever/fc-pregmod
  • jc052005/fc-pregmod
  • klorpa/fc-pregmod
  • doku/fc-pregmod
  • samhradh/fc-pregmod
  • scientist/fc-pregmod
  • albania420/fc-pregmod
  • Editoranon/fc-pregmod
  • Anony/fc-pregmod
  • deimios666/fc-pregmod
  • IvoHoe/fc-pregmod
  • bitty/fc-pregmod
  • RealAnon1800/fc-pregmod
  • brankirk/fc-pregmod
  • Amygdalan/fc-pregmod
  • DankWolf/fc-pregmod
  • Supot1951/fc-pregmod
  • bcy603/fc-pregmod
  • pwfxjpuv/fc-pregmod
  • ThreadAnon/fc-pregmod
  • Anon1800/fc-pregmod
  • Echoart/fc-pregmod
  • Dingotush/fc-pregmod
  • anonman/fc-pregmod
  • Arkerthan/fc-pregmod
  • svornost/fc-pregmod
  • wierdwierdos/fc-pregmod
  • wetwareAnon/fc-pregmod
  • QuartzHat/fc-pregmod
  • IchthysdeKilt/fc-pregmod
  • AnonAnonFC/fc-pregmod
  • Alexsis/fc-pregmod
  • LoyalTreeWP/fc-pregmod
  • aerialace/fc-pregmod
  • NurseryAnon/fc-pregmod
  • drakeashordcataclysm/fc-pregmod
  • AshVaris/fc-pregmod
  • purely0nothing/fc-pregmod
  • alex2011/fc-pregmod
  • Lindontree/fc-pregmod
  • FCaa/fc-pregmod
  • TR-8R/fc-pregmod
  • Jones/fc-pregmod
  • brr99/fc-pregmod
  • WriteAnon101/fc-pregmod
  • Drosil/fc-pregmod
  • Bob1221/fc-pregmod
  • vas/fc-pregmod
  • gitgud.user.937/fc-pregmod
  • D-K/fc-pregmod
  • AnonDev/fc-pregmod
  • madman23456/fc-pregmod
  • InarusLynx/fc-pregmod
  • Sonofrevvan/fc-pregmod
  • Randoisrando/fc-pregmod
  • cheez94/fc-pregmod
  • dldldl/fc-pregmod
  • alice321/fc-pregmod
  • Alexei91/fc-pregmod
  • darkcy/fc-pregmod
  • MapleMAD/fc-pregmod
  • pillarofsalt/fc-pregmod
  • vultureangels/fc-pregmod
  • kernel/fc-pregmod
  • nooneman/fc-pregmod
  • deepmurk/fc-pregmod
  • uglybead/fc-pregmod
  • lemongrab/fc-pregmod
  • temperence-chan/fc-pregmod
  • hcommenter/fc-pregmod
  • SpedeMemerson/fc-pregmod
  • qwijqwsf/fc-pregmod
  • BuDClow/fc-pregmod
  • HiveBro/fc-pregmod
  • shoku/fc-pregmod
  • ezsh/fc-pregmod
  • Blank/fc-pregmod
  • randoralcissian/fc-pregmod
  • benito92/fc-pregmod
  • balakart/fc-pregmod
  • wedonotsaw/fc-pregmod
  • Cayleth/fc-pregmod
  • Khip/fc-pregmod
  • Zfair/fc-pregmod
  • promethium/fc-pregmod
  • scyne/fc-pregmod
  • ZZC/fc-pregmod
  • SilverJanine/fc-pregmod
  • joxosix654email-9.co/fc-pregmod
  • Littlefootlittleguy/fc-pregmod
  • FelipeBA/fc-pregmod
  • bigtiddygothbf/fc-pregmod
  • Qotsafan/fc-pregmod
  • Zachpocalypse/fc-pregmod
  • milkanon66/fc-pregmod
  • GreGGoZZ/fc-pregmod
  • drsnarf86/fc-pregmod
  • valen102938/fc-pregmod
  • pregspammer/fc-pregmod
  • ponderin94/fc-pregmod
  • nook/fc-pregmod
  • carnifex34/fc-pregmod-mod-carni
  • SyntheticHigh/fc-pregmod
  • bob112211/fc-pregmod
  • amomynous0/fc-pregmod
  • oxone/fc-pregmod
  • MaxEuwe/fc-pregmod
  • nekoanon/fc-pregmod
  • preglocke/fc-pregmod
  • valen10293847/fc-pregmod
  • 2hu4u/fc-pregmod
  • mayibrad/fc-pregmod
  • Screm/fc-pregmod
  • Ansopedi/fc-pregmod
  • mrchaosbones/fc-pregmod
  • putrid/fc-pregmod
  • Kinnerman/fc-pregmod
  • gungrave1155/fc-pregmod
  • prndev/fc-pregmod
  • weresmilodon/fc-pregmod
  • auxxigobin/fc-pregmod
  • alice-chan/fc-pregmod
  • wigglie/fc-pregmod
  • jrliltfgb/fc-pregmod
  • Lord.alek.shade/fc-pregmod
  • truetailthesquire/fc-pregmod
  • lowercasedonkey/fc-pregmod
  • alice-chan9/fc-pregmod
  • eroglyphics/fc-pregmod
  • taliyent/fc-pregmod
  • zenzombie90/fc-pregmod
  • kjarik/fc-pregmod
  • wriggler/fc-pregmod
  • midnightblue/fc-pregmod
  • faraen/fc-pregmod
  • sigurd.cole/fc-pregmod
  • FCbuganon/fc-pregmod
  • kidkinster/fc-pregmod
  • Kar_Dragon/fc-pregmod
  • Zhafier/fc-pregmod
  • crcaretti/fc-pregmod
  • anond/fc-pregmod
  • tempmania/fc-pregmod
  • Dhanze/fc-pregmod
  • EstaUnCachucha/fc-pregmod
  • oniAnon/fc-pregmod
  • plebian/fc-pregmod
  • maxd569/fc-pregmod
  • Levarn/fc-pregmod
  • pumpkinspice/fc-pregmod
  • GammaXai/fc-pregmod
  • DanBackslide/fc-pregmod
  • i107760/fc-pregmod
  • Absimiliard/fc-pregmod
  • AmbrosiaCheesecake/fc-pregmod
  • fuguer/fc-pregmod
  • Azurel/fc-pregmod
  • Fake_Dev/fc-pregmod
  • ddongsanda/fc-pregmod
  • Combine456/fc-pregmod
  • UnwrappedGodiva/fc-pregmod
  • toyRuberDucky/fc-pregmod
  • zmobie/fc-pregmod
  • chuongk/fc-pregmod
  • BigWalnuts/fc-pregmod
  • Birdstrike/fc-pregmod
  • r3d/fc-pregmod
  • mawspa/fc-pregmod
  • sushila/fc-pregmod
  • DeathShip/fc-pregmod
  • eggrollsandwich/fc-pregmod
  • krayken/fc-pregmod
  • Reman/fc-pregmod
  • dwiafgts/fc-pregmod
  • jort93/fc-pregmod
  • teruterubouzu/fc-pregmod
  • flut/fc-pregmod
  • john-normal/fc-pregmod
  • Jonathan2405/fc-pregmod
  • Tyrgalon/fc-pregmod
  • NovX/fc-pregmod
  • Star1/fc-pregmod
  • Transhumanist01/fc-pregmod
  • m1017242/fc-pregmod
  • Rizal98798/fc-pregmod
  • jamezu369/fc-pregmod
  • thisisawittyname/fc-pregmod
  • KnightBoulegard/fc-pregmod
  • jblack/fc-pregmod
  • Souldrainr/fc-pregmod
  • torbjornhub/fc-pregmod
  • turnop/fc-pregmod
  • breadedpigeon/fc-pregmod
  • fire.maker/fc-pregmod
  • Inahaze/fc-pregmod
  • Waerjak/fc-pregmod
  • Trashman1138/fc-pregmod
  • supanintendo/fc-pregmod
  • _no0neman/fc-pregmod
  • Weslo/fc-pregmod
  • qw89/fc-pregmod
  • EvilDruid/fc-pregmod
  • dt25/fc-pregmod
  • Raou/fc-pregmod
  • DDouFu/fc-pregmod
  • Mauno/fc-pregmod
  • PandemoniumPenguin/fc-pregmod
  • AngelPuppet/fc-pregmod
  • DasUser79/fc-pregmod
  • Keaeag3s/fc-pregmod
  • HazeHazeHaze/fc-pregmod
  • hpotato/fc-pregmod
  • owouchthatbloodyhurt/fc-pregmod
  • v7Silent/fc-pregmod
  • nickylass/fc-pregmod
  • ThePrimer/fc-pregmod
  • PineCone/fc-pregmod
  • bruhmomentum17/fc-pregmod
  • CheatDude/fc-pregmod
  • synnove/fc-pregmod
  • en_bees/fc-pregmod
  • seronis/fc-pregmod
  • Nepidinepnep/fc-pregmod
  • Titanninja/fc-pregmod
  • Elohiem/fc-pregmod
  • cocoajazz/fc-pregmod
  • tfwncagf/fc-pregmod
  • ChunkyMonke/fc-pregmod
  • Dracoman671/fc-pregmod
  • jgl/fc-pregmod
  • Inev/fc-pregmod
  • jbige/fc-pregmod
  • MonsterMate/fc-pregmod
  • Konstantin6961/fc-pregmod
  • darth_ashi/fc-pregmod
  • shinx/fc-pregmod
  • Anu/fc-pregmod
  • Greytide/fc-pregmod
  • Bonafidemetal/fc-pregmod
  • Peje/fc-pregmod
  • Hexfy98/fc-pregmod
  • TooSlow/fc-pregmod
  • SoGu/fc-pregmod
  • CloudyCoffee/fc-pregmod
  • Welptard/fc-pregmod
  • Ploc/fc-pregmod-ploc
  • rain-/fc-pregmod
  • Pecanus/fc-pregmod
  • Jhortrax/fc-pregmod
  • valleytwo/fc-pregmod
  • QCmd/fc-pregmod
  • kung-wada/fc-pregmod
  • LolGaye/fc-pregmod
  • Exspiravit1/fc-pregmod
  • jadeddog/fc-pregmod
  • buster-scruggs/fs-antebellum-revivalism
  • policia123/fc-pregmod
  • evrgentesee/fc-pregmod
  • rko127/fc-pregmod
  • ExcalGrip12/fc-pregmod
  • BlackAion/fc-pregmod
  • Boss2020/fc-pregmod
  • Lawled/fc-pregmod
  • shiro/fc-pregmod
  • Skavenkeri/fc-pregmod
  • PooPooDooDooHead/fc-pregmod
  • Dugee/fc-pregmod
  • Portal124/fc-pregmod-vore
  • Fekenol/fc-pregmod
  • elGuapo/fc-pregmod
  • KelioSteel/fc-pregmod
  • sldlddk/fc-pregmod
  • lumepanter/fc-pregmod
  • ryuhana/fc-pregmod
  • Nene1009yb/fc-pregmod
  • DontAskDontTell/fc-pregmod-extra-events
  • Dulgi/fc-pregmod
  • Jate/fc-pregmod
  • percy365/fc-pregmod
  • franklygeorge/fc-pregmod
  • Dragneel117/fc-pregmod
  • vl96/fc-pregmod
  • Gorlom/fc-economicmod
  • NotAlive/fc-pregmod
  • Heretek/fc-pregmod
  • joeshmo828282/fc-pregmod
  • deswes/fc-pregmod
  • Nanana21/fc-pregmod
  • Gbr6/fc-pregmod
  • RandomNecro/fc-pregmod
  • Trinidad/fc-pregmod
  • anonymousey/fc-pregmod
  • macaronideath/fc-pregmod
  • fcbleh/fc-pregmod
  • jk3000/fc-pregmod
  • Akane/fc-pregmod
  • TheBoi/fc-pregmod
  • Sheenariel/fc-pregmod
  • Metapod/multi-custom
  • Banyanael/fc-pregmod
  • frogge/fc-pregmod
  • idkkk12385/fc-pregmod
  • Mirarara/fc-pregmod
  • DeaDa/fc-pregmod-thedeal
  • CobraCommander/fc-pregmod
  • bicobus/fc-pregmod
  • CardcaptorRLH85/fc-pregmod
  • temp-ui-start/fc-pregmod
  • PresidentConvert/fc-pregmod
  • delizious/fc-pregmod
  • Ducati/fc-pregmod
  • DerangedLoner/fc-pregmod-development-fork
  • ProjectVictory/fc-pregmod
  • forecastle/fc-pregmod
  • Apathy/fc-pregmod
  • indf/fc-pregmod-dev
  • GavAndAlt/fc-pregmod
  • hagamablabla/fc-pregmod
  • Alaco/fc-pregmod
  • DCoded/fc-pregmod
  • LittlePlague/fc-pregmod
  • MissOnahole/fc-pregmod
  • ishy2317/fc-pregmod
  • nielkazama/fc-pregmod
  • Phobos/fc-pregmod
  • kraster/fc-pregmod
  • JasWS/fc-pregmod
  • FelixJS/fc-pregmod
  • NCherfaoui/fc-pregmod
  • MidnightMoose/fc-pregmod
  • jjjjjj/fc-pregmod
  • Cl0ver/fc-pregmod
  • Pythoniqus/fc-pregmod
  • JohnMolotov/fc-pregmod
  • anonymouspregmodder/fc-pregmod-anonymouspregmodder
  • Fanatey/fc-pregmod
  • Mizako/fc-pregmod
  • Nithhogg/fc-pregmod
  • Bluecoffee/fc-pregmod
380 results
Show changes
Showing
with 8564 additions and 556 deletions
#pragma once
#include "fchost_storage.h"
// storage handlers that can be registered directly to Javascript to enable fchost_storage backing
class FCHostStorageHandler : public CefV8Handler
{
public:
FCHostStorageHandler(bool _persistent) : persistent(_persistent) {};
virtual bool Execute(const CefString& name,
CefRefPtr<CefV8Value> object,
const CefV8ValueList& arguments,
CefRefPtr<CefV8Value>& retval,
CefString& exception) override;
private:
bool persistent;
IMPLEMENT_REFCOUNTING(FCHostStorageHandler);
};
void FCHostStorageRegister(const std::filesystem::path& persistPath, CefRefPtr<CefV8Value> object);
// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
#include <windows.h>
#include <KnownFolders.h>
#include <ShlObj.h>
#include "fchost_app.h"
std::filesystem::path FCHostApp::GetLocalStorePath()
{
PWSTR ppath;
SHGetKnownFolderPath(FOLDERID_Documents, 0, NULL, &ppath);
std::filesystem::path local_storage = ppath;
CoTaskMemFree(ppath);
return local_storage / L"FreeCities_Pregmod";
}
// Entry point function for all processes.
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow) {
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// Provide CEF with command-line arguments.
CefMainArgs main_args(hInstance);
// It will create the first browser instance in OnContextInitialized() after
// CEF has initialized.
CefRefPtr<FCHostApp> app(new FCHostApp);
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, app, nullptr);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
command_line->InitFromString(lpCmdLine);
return app->main(main_args, command_line.get());
}
FCHost/fchost/res/fchost.ico

21.9 KiB

// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by fchost.rc
//
#define IDI_FCHOST 100
// Avoid files associated with MacOS
#define _X86_
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC 1
#define _APS_NEXT_RESOURCE_VALUE 102
#define _APS_NEXT_COMMAND_VALUE 32700
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 102
#endif
#endif
#include "./utility.h"
CefString cef_string_from_path(const std::filesystem::path& p)
{
return CefString(p.native());
}
void cef_string_from_path(const std::filesystem::path& p, cef_string_t* str)
{
const auto& pstr = p.native();
#if defined(OS_WIN)
cef_string_from_wide(pstr.c_str(), pstr.size(), str);
#else
cef_string_from_utf8(pstr.c_str(), pstr.size(), str);
#endif
}
#pragma once
#include "include/capi/cef_base_capi.h"
#include <filesystem>
CefString cef_string_from_path(const std::filesystem::path& p);
void cef_string_from_path(const std::filesystem::path& p, cef_string_t* str);
# The workflow for updating the SugarCube format.js file
sugarcube:
echo "Compiling SugarCube"
(cd submodules/sugarcube-2/ && node build.js -n -b 2)
mv submodules/sugarcube-2/build/twine2/sugarcube-2/format.js devTools/tweeGo/storyFormats/sugarcube-2/format.js
(cd submodules/sugarcube-2/ && git diff master fc-rebased > sugarcube-fc-changes.patch)
mv submodules/sugarcube-2/sugarcube-fc-changes.patch devNotes/"sugarcube stuff"/sugarcube-fc-changes.patch
.PHONY: sugarcube
Common problems:
How do I start the game?
-Run the compile file, go to folder "bin", click the "FC_Pregmod" and play. (Recommendation: Drag it into incognito mode)
I get an error on gamestart reading "Apologies! A fatal error has occurred. Aborting. Error: Unexpected token @ in JSON at position 0. Stack Trace: SyntaxError: Unexpected token @ in JSON at position 0 at JSON.parse (<anonymous>) at JSON.value" or some variant
-clear cookies
Everything is broken!
-Do not copy over your existing download as it may leave old files behind, replace it entirely
I can't save more than once or twice.
-Known issue caused by sugarcube level changes. Save to file doesn't have this problem and will likely avoid the first problem as well.
-It is possible to increase the memory utilized by your browser to delay this
I wish to report a sanityCheck issue.
-Great, however a large majority of the results are false positives coming from those specific sections being split over several lines in the name of readability and git grep's intentionally (http://git.661346.n2.nabble.com/bug-git-grep-P-and-multiline-mode-td7613900.html ) lacking support for multiline. An Attempt to add -Pzl (https://gitgud.io/pregmodfan/fc-pregmod/merge_requests/2108 ) created a sub condition black hole. What follows are examples of common false positives that can safely be ignored;
[MissingClosingAngleBracket]src/art/vector/Generate_Stylesheet.tw:11:<<print "<style>."+_art_display_class+" {
<<print "<style>."+_art_display_class+" {
position: absolute;
height: 100%;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
}
How to mod (basic doc):
1. All sources now in the src subdir, in separate files. 1 passage = 1 file.
2. Special files and dir's:
- src/config - configuration of the story is here.
- src/config/start.tw - contains list of .tw passage files, regenerated automatic, by building scripts. Do not change by hands. (original passage Start from pregmod renamed and moved to src/events/intro/introSummary.tw)
- src/js/storyJS.tw - special passage with [script] tag - contain all native JavaScript from pregmod.
- devTools/tweeGo/targets/sugarcube-2/userlib.js - on original FC JS moved here (I deleted it after moving JS to storyJS.tw). Compare to storyJS.tw but do not copy file here. May conflict with src/js/storyJS.tw if copied.
- src/pregmod - I put all pregmod-only passages here.
- .gitignore - special file for git - to ignore some files. For example - compilation results.
3. Compilation:
Windows:
Run compile.bat - result will be file bin/FC_pregmod.html
Second run of compile.but will overwrite bin/FC_pregmod.html without prompt.
Linux:
Ensure executable permission on file "devTools/tweeGo/tweego" (not tweego.exe!)
Ensure executable permission on file "compile.sh"
In the root dir of sources (where you see src, devTools, bin...) run command "./compile.sh" from console
Mac:
Not supported directly (I don't have access to Mac for testing).
But you can use linux compilation script if you download tweego for mac from here: https://bitbucket.org/tmedwards/tweego/downloads/ and replace linux executable with mac executable in ./devTools/tweeGo/ folder. This is not tested though, so be warned.
4. Simple comparing and merging with original FC:
Use meld tool. Place folder FreeCities (original FC sources tree) near FreeCitiesPregmod (this sources tree) and use command:
meld FreeCities FreeCitiesPregmod
or just select these folders in meld's GUI.
5. All modders will be very grateful if anyone who makes some changes to game with .html file also post their resulting src folder tree.
6. For contributors to pregmod: if you don't use git, then you need to post your version of src folder tree, not just produced FC_pregmod.html file!!! This html file can't be reverted to proper sources, and useless as contribution!
7. Git workflow:
- Master branch is pregmod-master. Only Pregmodder can add something to it directly. Always contain his last public changes.
- pregmod-dev - branch with experimental code mainly by pregmodfan.
- Any contributions will be placed in separate branches like pregmod-mod-<something> (if it's ready to merge with master complete feature/mod) or pregmod-contrib-<something> if it's partial work until contributions is reviewed.
Typical cycle with git:
1. Make account on gitgud if you don't have usable one.
2. Fork main repository through gitgud interface. (Or pull changes from main repo if you already have fork.)
3. Clone your fork to local machine with git client (Or pull changes if already cloned.)
4. Make you changes as you like, commit, and push result into your forked repository (with git client).
5. Make merge request through gitgud interface.
<!-- cSpell:ignore nwjs -->
# Free Cities - pregmod
Pregmod is a modification of the original [Free Cities](https://freecitiesblog.blogspot.com/) created by FCdev.
## Play the game
* WARNING - FCHost users need to update or rebuild FCHost before running alpha.34 or later (or the latest build) or it WILL DELETE YOUR LOCAL SAVES!!! Download info is at https://gitgud.io/pregmodfan/fc-pregmod/-/blob/pregmod-master/FCHost/README.md?ref_type=heads#fchost and build info is at https://gitgud.io/pregmodfan/fc-pregmod/-/blob/pregmod-master/FCHost/HowToBuild.md#how-to-build
1. Download the game
* [Current release](https://gitgud.io/pregmodfan/fc-pregmod/-/releases)
* [Latest build](https://gitgud.io/pregmodfan/fc-pregmod/-/jobs/artifacts/pregmod-master/download?job=build)
2. Open the game in your preferred browser
* On PC, we recommend either Firefox or [FCHost](FCHost/README.md).
* Recommendation: Drag it into incognito mode
3. Have fun!
### Compile the game yourself
If you want to tweak the game a bit, you can easily download the files and compile it yourself.
1. Clone the git repository:
1. [Install Git for terminal](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) or a Git GUI of your
choice.
2. Clone the repo
* Via terminal: `git clone --single-branch https://gitgud.io/pregmodfan/fc-pregmod.git`
3. Get updates
* Via terminal: `git pull`
2. Compile the game:
* Using one of two methods
1. The simple compiler by running `simple-compiler.bat` (Windows) or `simple-compiler.sh` (Mac/Linux)
* Benefits:
* Requires no external dependencies
* Slightly faster compiling
2. The advanced compiler by running `compile.bat` (Windows) or `compile.sh` (Mac/Linux)
* Requires:
* [Git](https://git-scm.com/)
* [Node.js](https://nodejs.org)
* ~500 MB of Node packages
* `compile.[bat, sh]` will attempt to help you with the installation of its dependencies
* Benefits:
* Easier debugging
* Early problem detection
* Spell checking
* Tweaking of compiler settings by running `setup.bat` (Windows) or `setup.sh` (Mac/Linux)
* Copies `FC_pregmod.html` to `FCHost` if it is installed
* Live reloading of FC after file changes by running `watcher.bat` (Windows) or `watcher.sh` (Mac/Linux)
* We suggest using the advanced compiler when possible.
* **The second run of the compiler will overwrite the existing `FC_pregmod.html` file!**
* **All our tooling expects that you are running them in FC's root directory** (Where you see devTools, src, js, etc). Failure to do so will result in errors.
* If you are using an ARM-based device, you may need to use `arch -x86_64 bash [compile or simple-compiler].sh` to properly compile.
3. To play open `FC_pregmod.html` in the `bin/` folder
* Repeat steps 2 and 3 after you make any changes or use `watcher.[bat, sh]` to do them automatically.
## Common problems
* If compiling takes a long time (more than 2 minutes) or causes a noticeable increase in system resource utilisation.
- FC's compiler makes a lot of file changes over a short period of time. Some Antivirus programs will make FC's compiler wait while it scans the contents of each changed file. So it might be worth making sure FC's directory is excluded in your Antivirus settings.
* If your Antivirus is Windows Defender (currently tested with Windows 10 on 04/14/2024):
* `Start menu` -> `Windows Security` -> `Virus & threat protection` -> `Virus & threat protection settings` ->
`Manage settings` -> `Exclusions (near the bottom)` -> `Add or remove exclusions` -> `Add an exclusion` ->
`path to FC's root directory (Where you see devTools, src, js, etc).`
* `sessionStorage quota exceeded` / `localStorage quota exceeded` or something similar
- Your saves stored inside the browser are getting too large. There are multiple ways to solve this:
1. Delete saves stored in the browser. If you want to keep them, save them to disk first.
2. Disable autosave and delete the current one. Due to technical reasons autosaves are larger than normal saves, so this may help more than expected.
3. If on Firefox, raise the storage limit: Type `about:config` in the address bar and search for
`dom.storage.default_quota`. Increase this value as needed. Default value is 5120 kilobytes / 5 MB.
4. Switch to a different browser. Recommended is either Firefox or [FCHost](FCHost/README.md), a custom HTML renderer specifically for Pregmod.
5. If you absolutely need to use Google Chrome:
1. download and unzip [NW.js SDK](https://nwjs.io/downloads/) for your operative system.
2. copy the game file (FC_pregmod.html) into the `nwjs-sdk-v0.XX.Y-YOUR_OS` folder
3. in the same folder, create a text file with the following content:
```
{
"name": "Free Cities pregmod edition",
"main": "FC_pregmod.html",
"dom_storage_quota":30
}
```
and save it as package.json. In this example, 30 is the limit (in MB) that is set for the storage quota,
but you can replace it with any number. Google Chrome has the same default value as Firefox.
4. Double click nw.exe to launch the game.
* Everything is broken!
- **Do not copy over your existing download** as it may leave old files behind, replace it entirely
* I can't save more than once or twice.
- Known issue caused by SugarCube level changes. Save to file doesn't have this problem and will likely avoid the
first problem as well.
- It is possible to increase the memory utilized by your browser to delay this
* I wish to report an issue.
1. Search [issues](https://gitgud.io/pregmodfan/fc-pregmod/-/issues) to see if someone has already reported the issue.
2. [Open a new issue](https://gitgud.io/pregmodfan/fc-pregmod/-/issues/new) or, if you are interested in trying to fix it yourself, please see our guide on [contributing](CONTRIBUTING.md).
## Contribute
New Contributors are always welcome. Basic information before you start can be found [here](CONTRIBUTING.md).
## Submodules
FC uses a modified version of SugarCube 2. More information can be found [here](devNotes/sugarcube stuff/building SugarCube.md).
Further Development:
- specialized slave schools
- fortifications
- more levels for militia edict (further militarize society)
- conquering other arcologies?
Events:
- famous criminal escapes to the arcology, followed by another arcology's police force
Bugs:
- sometimes troop counts breaks
- sometimes rebel numbers have fractionary parts
Rules Assistant:
- find a way for the new intense drugs to fit in
- everything mentioned in https://gitgud.io/pregmodfan/fc-pregmod/issues/81
main.tw porting:
- slaveart
- createsimpletabs
- displaybuilding
- optionssortasappearsonmain
- resetassignmentfilter
- mainlinks
- arcology description
- office description
- slave summary
- use guard
- toychest
- walk past
<!-- cSpell:ignore faraen, eart, lxml, XMLID -->
# How to vector art
Please read the whole document before starting to work.
......@@ -36,6 +38,48 @@ While editing, keep the Layers in mind.
* There are some globally available styles defined as CSS classes (e.g. skin, hair).
Use them if your asset should be changed upon display.
Do not set the style directly in the object.
* You can use Inkscape to define and add classes to paths.
To add a class to a path, open the XML editor with the path selected (Edit → XML Editor...)
Delete all fields except for those with the names 'id', and 'd'.
Add a new field to the path and name it 'class'.
Make its value one of the values defined in the below section.
**MAINTAIN THESE CLASS DEFINITIONS BY MANUALLY EDITING YOUR WORKING SVG FILE**
<style
type="text/css"
id="style">
/* please maintain these definitions manually */
.white{fill:#FFFFFF;}
.skin{fill:#F6E0E8;}
.head{}
.eart{}
.torso{}
.tail{}
.boob{}
.penis{}
.scrotum{}
.areola{fill:#D76B93;}
.bellybutton{fill:#D76B93;}
.labia{fill:#D76B93;}
.hair{fill:#3F403F;}
.eyebrow_hair{fill:#3F403F;}
.shoe{fill:#3E65B0;}
.shoe_shadow{fill:#15406D;}
.smart_piercing{fill:#4DB748;}
.steel_piercing{fill:#787878;}
.steel_chastity{fill:#BABABA;}
.gag{fill:#BF2126;}
.shadow{fill:#010101;}
.glasses{}
.eye{}
.sclera{fill:#FFFFFF;}
.lips{}
</style>
You should be able to find this structure somewhere in the first 500 lines of your working svg file.
The formatting of these definitions matters for `normalize_svg.py`, which is invoked as part of `vector_layer_split.py` and explained below.
## 3. Normalize the document (before committing)
......@@ -48,7 +92,7 @@ Use
before committing to normalize the format so git will not freak out about the changed indentation.
If you use Inkscape, please use in Edit → Settings → Input/Output → SVG-Output → Path Data → Optimized. This is the default.
If you use Inkscape, please use Edit → Settings → Input/Output → SVG-Output → Path Data → Optimized. This is the default.
In case your Editor uses another path data style which cannot be changed, please contact the other artists and developers via the issue tracker to find a new common ground.
What it does:
......
This diff is collapsed.
#!/usr/bin/env python3
'''
"""
Application for "normalizing" SVGs
These problems are addressed:
......@@ -13,75 +13,125 @@ https://bugs.launchpad.net/inkscape/+bug/167937
Usage Example:
python3 inkscape_svg_fixup.py vector_source.svg
'''
"""
import lxml.etree as etree
import re
import sys
import lxml.etree as etree
color_classes = {
'skin', 'head', 'torso', 'boob', 'penis', 'scrotum', 'belly', 'areola', 'bellybutton', 'labia', 'hair',
'pubic_hair', 'armpit_hair', 'eyebrow_hair', 'shoe', 'shoe_shadow', 'smart_piercing', 'steel_piercing',
'steel_chastity', 'outfit_base', 'gag', 'shadow', 'glasses', 'eye', 'sclera', 'eart', 'tail',
'white', 'skin', 'skin_highlight', 'skin_shade', 'skin_strong_highlight', 'skin_strong_shade', 'arm',
'arm_highlight', 'arm_shade', 'head', 'head_highlight', 'head_shade', 'torso', 'torso_highlight',
'torso_shade', 'boob', 'boob_highlight', 'boob_shade', 'penis', 'penis_highlight', 'penis_shade',
'scrotum', 'scrotum_highlight', 'scrotum_shade', 'belly', 'belly_highlight', 'belly_shade', 'neck',
'neck_highlight', 'neck_shade', 'legs', 'legs_highlight', 'legs_shade', 'butt', 'butt_highlight',
'butt_shade', 'feet', 'feet_highlight', 'feet_shade', 'areola', 'labia', 'hair', 'shoe_shadow',
'smart_piercing', 'steel_piercing', 'steel_chastity', 'gag', 'shadow', 'glasses', 'lips', 'eyeball',
'iris', 'highlight1', 'highlight2', 'highlight3', 'highlightStrong', 'armpit_hair', 'pubic_hair',
'muscle_tone', 'belly_details', 'shoe_primary', 'shoe_primary_highlight', 'shoe_primary_shade',
'shoe_accent', 'shoe_accent_highlight', 'shoe_accent_shade', 'top_primary', 'top_primary_highlight',
'top_primary_shade', 'top_accent', 'top_accent_highlight', 'top_accent_shade', 'bottoms_primary',
'bottoms_primary_highlight', 'bottoms_primary_shade', 'bottoms_accent', 'bottoms_accent_highlight',
'bottoms_accent_shade', 'bra_primary', 'bra_primary_highlight', 'bra_primary_shade', 'bra_accent',
'bra_accent_highlight', 'bra_accent_shade', 'bra_strap1', 'bra_strap2', 'bra_strap3', 'shirt_center1',
'shirt_center2', 'shirt_center3', 'panties_primary', 'panties_primary_highlight', 'panties_primary_shade',
'panties_accent', 'panties_accent_highlight', 'panties_accent_shade', 'stockings_primary',
'stockings_accent', 'top_primary_strong_highlight', 'top_primary_strong_shade',
'top_accent_strong_highlight', 'top_accent_strong_shade', 'bottoms_primary_strong_highlight',
'bottoms_primary_strong_shade', 'bottoms_accent_strong_highlight', 'bottoms_accent_strong_shade',
'bellymask_1', 'bellymask_2', 'bellymask_3', 'bellymask_4', 'bellymask_5', 'bellymask_6', 'bellymask_7',
'bellymask_8', 'bellymask_9', 'bellymask_normal', 'bellymask_hourglass', 'bellymask_unnatural', "feet_nails"
}
def fix(tree):
# know namespaces
ns = {
'svg' : 'http://www.w3.org/2000/svg',
'inkscape' : 'http://www.inkscape.org/namespaces/inkscape'
}
# know namespaces
ns = {
'svg': 'http://www.w3.org/2000/svg',
'inkscape': 'http://www.inkscape.org/namespaces/inkscape'
}
# find document global style definition
# mangle it and interpret as python dictionary
style_element = tree.find('./svg:style',namespaces=ns)
style_definitions = style_element.text
pythonic_style_definitions = '{'+style_definitions.\
replace('\t.','"').replace('{','":"').replace('}','",').\
replace('/*','#')+'}'
styles = eval(pythonic_style_definitions)
# find document global style definition
# mangle it and interpret as python dictionary
style_element = tree.find('./svg:style', namespaces=ns)
style_definitions = style_element.text
pythonic_style_definitions = '{' + style_definitions. \
replace('\t.', '"').replace('{', '":"').replace('}', '",'). \
replace('/*', '#') + '}'
styles = eval(pythonic_style_definitions)
# go through all SVG elements
for elem in tree.iter():
if (elem.tag == etree.QName(ns['svg'], 'g')):
# compare inkscape label with group element ID
l = elem.get(etree.QName(ns['inkscape'], 'label'))
if l:
i = elem.get('id')
if (i != l):
print("Overwriting ID %s with Label %s..."%(i, l))
elem.set('id', l)
# go through all SVG elements
for elem in tree.iter():
if elem.tag == etree.QName(ns['svg'], 'g'):
# compare inkscape label with group element ID
lbl = elem.get(etree.QName(ns['inkscape'], 'label'))
if lbl:
i = elem.get('id')
if i != lbl:
print("Overwriting ID %s with Label %s..." % (i, lbl))
elem.set('id', lbl)
# clean styles (for easier manual merging)
style_string = elem.get('style')
if style_string:
split_styles = style_string.strip('; ').split(';')
styles_pairs = [s.strip('; ').split(':') for s in split_styles]
filtered_pairs = [(k, v) for k, v in styles_pairs if not (
k.startswith('font-') or
k.startswith('text-') or
k.endswith('-spacing') or
k in ["line-height", " direction", " writing", " baseline-shift", " white-space", " writing-mode"]
)]
split_styles = [':'.join(p) for p in filtered_pairs]
style_string = ';'.join(sorted(split_styles))
elem.attrib["style"] = style_string
# clean styles (for easier manual merging)
style_string = elem.get('style')
if style_string:
split_styles = style_string.strip('; ').split(';')
styles_pairs = [s.strip('; ').split(':') for s in split_styles]
filtered_pairs = [ (k,v) for k,v in styles_pairs if not (
k.startswith('font-') or
k.startswith('text-') or
k.endswith('-spacing') or
k in ["line-height", " direction", " writing", " baseline-shift", " white-space", " writing-mode"]
)]
split_styles = [':'.join(p) for p in filtered_pairs]
style_string = ';'.join(sorted(split_styles))
elem.attrib["style"] = style_string
# remove all style attributes offending class styles
s = elem.get('style')
c = elem.get('class')
if c and s:
if 'i' in locals():
s = s.lower()
c = c.split(' ')[0] # regard main style only
classes = c.split(' ')
has_color_class = any(x in color_classes for x in classes)
if has_color_class:
s_new = re.sub('fill:#[0-9a-f]+;?', '', s)
if s != s_new:
print("Explicit fill was removed from style string ({0}) for element with ID {1} "
"because its class ({2}) controls the fill color".format(s, i, c))
s = s_new
if s == 'style=""': # the style is empty now
del elem.attrib["style"]
continue
cs = ''
if c in styles:
cs = styles[c].strip('; ').lower()
if c not in styles:
print("Object id %s references unknown style class %s." % (i, c))
else:
if cs != s.strip('; '):
print("Style %s removed from object id %s differed from class %s style %s." % (s, i, c, cs))
del elem.attrib["style"]
else:
print('--------------------------- i not defined - propably a clip-path with a style ---------------------------')
print("Element tag: ".format(elem.tag))
print("Element style: ".format(s))
print("Element class: ".format(c))
print(etree.tostring(elem, pretty_print=True))
print('--------------------------- i not defined - propably a clip-path with a style ---------------------------')
# remove explicit fill color if element class is one of the color_classes
# remove all style attributes offending class styles
s = elem.get('style')
c = elem.get('class')
if (c and s):
s = s.lower()
c = c.split(' ')[0] # regard main style only
cs = ''
if c in styles:
cs = styles[c].strip('; ').lower()
if (c not in styles):
print("Object id %s references unknown style class %s."%(i,c))
else:
if (cs != s.strip('; ')):
print("Style %s removed from object id %s differed from class %s style %s."%(s,i,c,cs))
del elem.attrib["style"]
if __name__ == "__main__":
input_file = sys.argv[1]
tree = etree.parse(input_file)
fix(tree)
# store SVG into file (input file is overwritten)
svg = etree.tostring(tree, pretty_print=True)
with open(input_file, 'wb') as f:
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
input_file = sys.argv[1]
tree = etree.parse(input_file)
fix(tree)
# store SVG into file (input file is overwritten)
svg = etree.tostring(tree, pretty_print=True)
with open(input_file, 'wb') as f:
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
import os
import subprocess
import sys
if os.name == "nt":
minifier = "../devTools/minify/minify_win_amd64.exe"
elif sys.platform == "darwin":
minifier = "../devTools/minify/minify_darwin_amd64"
else:
# probably linux, if not we don't have an alternative anyways.
minifier = "../devTools/minify/minify_linux_amd64"
def save(layer, prefix, output_directory, output_format, svg_data):
i = layer.get('id')
output_path = os.path.join(output_directory, "{0}{1}.svg".format(prefix, i))
if output_format == 'svg':
with open(output_path, 'wb') as f:
# Header for normal SVG (XML)
f.write(f'<?xml version="1.0" encoding="UTF-8" standalone="no"?>{os.linesep}'.encode("utf-8"))
f.write(svg_data)
elif output_format == 'tw':
# send to minifier through stdin, which then saves to file
subprocess.run([minifier, "--mime=image/svg+xml", "-o", output_path],
input=svg_data)
#!/usr/bin/env python3
'''
"""
Application for procedural content adaption
*THIS IS VERY EXPERIMENTAL*
Contains a very poor man's implementation of spline mesh warping.
......@@ -23,15 +23,16 @@ python3 vector_clothing_replicator.py infile clothing bodypart destinationfile
Usage Example:
python3 vector_clothing_replicator.py vector_source.svg Straps Boob vector_destination.svg
python3 vector_clothing_replicator.py vector_source.svg Straps Torso vector_destination.svg
'''
"""
from svg.path import parse_path
import copy
import lxml.etree as etree
import sys
import lxml.etree as etree
from svg.path import parse_path
REFERENCE_PATH_SAMPLES = 200
EMBED_REPLICATIONS = True # whether to embed all replications into the input file or output separate files
EMBED_REPLICATIONS = True # whether to embed all replications into the input file or output separate files
input_file = sys.argv[1]
clothing = sys.argv[2]
......@@ -39,121 +40,127 @@ bodypart = sys.argv[3]
output_file_embed = sys.argv[4]
# TODO: make these configurable
output_file_pattern = '%s_%s_%s.svg' #bodypart, target_id, clothing
if ('Torso' == bodypart):
xpath_shape = './svg:g[@id="Torso_"]/svg:g[@id="Torso_%s"]/svg:path[@class="skin torso"]/@d' # TODO: formulate more general, independent of style
xpath_outfit_container = '//svg:g[@id="Torso_Outfit_%s_"]'%(clothing)
xpath_outfit = '//svg:g[@id="Torso_Outfit_%s_%s"]'%(clothing,'%s')
target_ids = "Unnatural,Hourglass,Normal".split(",")
reference_id = "Hourglass"
output_file_pattern = '%s_%s_%s.svg' # bodypart, target_id, clothing
if 'Torso' == bodypart:
xpath_shape = './svg:g[@id="Torso_"]/svg:g[@id="Torso_%s"]/svg:path[@class="skin torso"]/@d' # TODO: formulate more general, independent of style
xpath_outfit_container = '//svg:g[@id="Torso_Outfit_%s_"]' % (clothing)
xpath_outfit = '//svg:g[@id="Torso_Outfit_%s_%s"]' % (clothing, '%s')
target_ids = "Unnatural,Hourglass,Normal".split(",")
reference_id = "Hourglass"
else:
raise RuntimeError("Please specify a bodypart for clothing to replicate.")
raise RuntimeError("Please specify a bodypart for clothing to replicate.")
tree = etree.parse(input_file)
ns = {'svg' : 'http://www.w3.org/2000/svg'}
ns = {'svg': 'http://www.w3.org/2000/svg'}
canvas = copy.deepcopy(tree)
for e in canvas.xpath('./svg:g',namespaces=ns)+canvas.xpath('./svg:path',namespaces=ns):
# TODO: this should be "remove all objects, preserve document properties"
e.getparent().remove(e)
for e in canvas.xpath('./svg:g', namespaces=ns) + canvas.xpath('./svg:path', namespaces=ns):
# TODO: this should be "remove all objects, preserve document properties"
e.getparent().remove(e)
def get_points(xpath_shape):
'''
This function extracts reference paths by the given xpath selector.
Each path is used to sample a fixed number of points.
'''
paths_data = tree.xpath(xpath_shape,namespaces=ns)
points = []
path_length = None
for path_data in paths_data:
p = parse_path(path_data)
points += [
p.point(1.0/float(REFERENCE_PATH_SAMPLES)*i)
for i in range(REFERENCE_PATH_SAMPLES)
]
if (not points):
raise RuntimeError(
'No paths for reference points found by selector "%s".'%(xpath_shape)
)
return points
"""
This function extracts reference paths by the given xpath selector.
Each path is used to sample a fixed number of points.
"""
paths_data = tree.xpath(xpath_shape, namespaces=ns)
points = []
path_length = None
for path_data in paths_data:
p = parse_path(path_data)
points += [
p.point(1.0 / float(REFERENCE_PATH_SAMPLES) * i)
for i in range(REFERENCE_PATH_SAMPLES)
]
if not points:
raise RuntimeError(
'No paths for reference points found by selector "%s".' % xpath_shape
)
return points
def point_movement(point, reference_points, target_points):
'''
For a given point, finds the nearest point in the reference path.
Gives distance vector from the nearest reference point to the
respective target reference point.
'''
distances = [abs(point-reference_point) for reference_point in reference_points]
min_ref_dist_idx = min(enumerate(distances), key=lambda x:x[1])[0]
movement = target_points[min_ref_dist_idx] - reference_points[min_ref_dist_idx]
return movement
reference_points = get_points(xpath_shape%(reference_id))
container = tree.xpath(xpath_outfit_container,namespaces=ns)
if (len(container) != 1):
raise RuntimeError('Outfit container selector "%s" does not yield exactly one layer.'%(xpath_outfit_container))
"""
For a given point, finds the nearest point in the reference path.
Gives distance vector from the nearest reference point to the
respective target reference point.
"""
distances = [abs(point - reference_point) for reference_point in reference_points]
min_ref_dist_idx = min(enumerate(distances), key=lambda x: x[1])[0]
movement = target_points[min_ref_dist_idx] - reference_points[min_ref_dist_idx]
return movement
reference_points = get_points(xpath_shape % reference_id)
container = tree.xpath(xpath_outfit_container, namespaces=ns)
if len(container) != 1:
raise RuntimeError('Outfit container selector "%s" does not yield exactly one layer.' % xpath_outfit_container)
container = container[0]
outfit_source = container.xpath(xpath_outfit%(reference_id),namespaces=ns)
if (len(outfit_source) != 1):
raise RuntimeError('Outfit source selector "%s" does not yield exactly one outfit layer in container selected by "%s".'%(xpath_outfit%(reference_id), xpath_outfit_container))
outfit_source = container.xpath(xpath_outfit % reference_id, namespaces=ns)
if len(outfit_source) != 1:
raise RuntimeError(
'Outfit source selector "%s" does not yield exactly one outfit layer in container selected by "%s".' % (
xpath_outfit % reference_id, xpath_outfit_container))
outfit_source = outfit_source[0]
for target_id in target_ids:
print(
'Generating variant "%s" of clothing "%s" for bodypart "%s"...'%
(target_id, clothing, bodypart)
)
outfit = copy.deepcopy(outfit_source)
paths = outfit.xpath('./svg:path',namespaces=ns)
if target_id == reference_id:
print("This is the source variant. Skipping...")
else:
layerid = outfit.get('id').replace('_%s'%(reference_id),'_%s'%(target_id))
outfit.set('id', layerid)
outfit.set(etree.QName('http://www.inkscape.org/namespaces/inkscape', 'label'), layerid) # for the Inkscape-users
target_points = get_points(xpath_shape%(target_id))
if (len(reference_points) != len(target_points)):
raise RuntimeError(
('Different amounts of sampled points in reference "%s" and target "%s" paths. '+
'Selector "%s" probably matches different number of paths in the two layers.')%
(reference_id, target_id, xpath_shape)
)
for path in paths:
path_data = path.get("d")
p = parse_path(path_data)
for segment in p:
original_distance = abs(segment.end-segment.start)
start_movement = point_movement(segment.start, reference_points, target_points)
segment.start += start_movement
end_movement = point_movement(segment.end, reference_points, target_points)
segment.end += end_movement
distance = abs(segment.end-segment.start)
try:
# enhance position of CubicBezier control points
# amplification is relative to the distance gained by movement
segment.control1 += start_movement
segment.control1 += (segment.control1-segment.start)*(distance/original_distance-1.0)
segment.control2 += end_movement
segment.control2 += (segment.control2-segment.end)*(distance/original_distance-1.0)
except AttributeError as ae:
# segment is not a CubicBezier
pass
path.set("d", p.d())
if EMBED_REPLICATIONS:
container.append(outfit)
if not EMBED_REPLICATIONS:
container = copy.deepcopy(canvas).xpath('.',namespaces=ns)[0]
container.append(outfit)
if not EMBED_REPLICATIONS:
svg = etree.tostring(container, pretty_print=True)
with open((output_file_pattern%(bodypart, target_id, clothing)).lower(), 'wb') as f:
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
print(
'Generating variant "%s" of clothing "%s" for bodypart "%s"...' %
(target_id, clothing, bodypart)
)
outfit = copy.deepcopy(outfit_source)
paths = outfit.xpath('./svg:path', namespaces=ns)
if target_id == reference_id:
print("This is the source variant. Skipping...")
else:
layerid = outfit.get('id').replace('_%s' % reference_id, '_%s' % target_id)
outfit.set('id', layerid)
outfit.set(etree.QName('http://www.inkscape.org/namespaces/inkscape', 'label'),
layerid) # for the Inkscape-users
target_points = get_points(xpath_shape % target_id)
if len(reference_points) != len(target_points):
raise RuntimeError(
('Different amounts of sampled points in reference "%s" and target "%s" paths. ' +
'Selector "%s" probably matches different number of paths in the two layers.') %
(reference_id, target_id, xpath_shape)
)
for path in paths:
path_data = path.get("d")
p = parse_path(path_data)
for segment in p:
original_distance = abs(segment.end - segment.start)
start_movement = point_movement(segment.start, reference_points, target_points)
segment.start += start_movement
end_movement = point_movement(segment.end, reference_points, target_points)
segment.end += end_movement
distance = abs(segment.end - segment.start)
try:
# enhance position of CubicBezier control points
# amplification is relative to the distance gained by movement
segment.control1 += start_movement
segment.control1 += (segment.control1 - segment.start) * (distance / original_distance - 1.0)
segment.control2 += end_movement
segment.control2 += (segment.control2 - segment.end) * (distance / original_distance - 1.0)
except AttributeError as ae:
# segment is not a CubicBezier
pass
path.set("d", p.d())
if EMBED_REPLICATIONS:
container.append(outfit)
if not EMBED_REPLICATIONS:
container = copy.deepcopy(canvas).xpath('.', namespaces=ns)[0]
container.append(outfit)
if not EMBED_REPLICATIONS:
svg = etree.tostring(container, pretty_print=True)
with open((output_file_pattern % (bodypart, target_id, clothing)).lower(), 'wb') as f:
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
if EMBED_REPLICATIONS:
svg = etree.tostring(tree, pretty_print=True)
with open(output_file_embed, 'wb') as f:
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
svg = etree.tostring(tree, pretty_print=True)
with open(output_file_embed, 'wb') as f:
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
This diff is collapsed.
......@@ -64,7 +64,7 @@
<inkscape:grid type="xygrid" id="grid897"/>
</sodipodi:namedview>
<style type="text/css" id="style">
/* please maintain these definitions manually */
/* please maintain these definitions manually */
.white{fill:#FFFFFF;}
.skin{fill:#F6E0E8;}
.head{}
This diff is collapsed.
......@@ -64,7 +64,7 @@
<inkscape:grid type="xygrid" id="grid897"/>
</sodipodi:namedview>
<style type="text/css" id="style">
/* please maintain these definitions manually */
/* please maintain these definitions manually */
.white{fill:#FFFFFF;}
.skin{fill:#F6E0E8;}
.head{}
#!/usr/bin/env python3
'''
"""
Application for splitting groups from one SVG file into separate files
Usage:
......@@ -8,117 +8,128 @@ python3 vector_layer_split.py infile format outdir
Usage Example:
python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/
'''
"""
import lxml.etree as etree
import sys
import os
import argparse
import copy
import os
import re
import lxml.etree as etree
import normalize_svg
import svg_split_utils
parser = argparse.ArgumentParser(
description='Application for splitting groups from one SVG file into separate files.')
parser.add_argument('-o', '--output', dest='output_dir', required=True,
help='output directory')
parser.add_argument('-f', '--format', dest='output_format',
choices=['svg', 'tw'], default='svg', help='output format.')
parser.add_argument('-p', '--prefix', dest='prefix', default='',
help='Prepend this string to result file names')
parser.add_argument('input_file', metavar='FILENAME', nargs='+',
help='Input SVG file with layers')
args = parser.parse_args()
output_format = args.output_format
output_directory = args.output_dir
def split_file(input_file):
tree = etree.parse(input_file)
normalize_svg.fix(tree)
ns = {
'svg': 'http://www.w3.org/2000/svg',
'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
'sodipodi': "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
}
# strip undesired editor attributes from the tree
etree.strip_attributes(tree, f"{{{ns['inkscape']}}}*", f"{{{ns['sodipodi']}}}*");
# prepare output template
template = copy.deepcopy(tree)
root = template.getroot()
# remove all svg root attributes except document size
for a in root.attrib:
if a != "viewBox":
del root.attrib[a]
# remove all content, including metadata
# for twine output, style definitions are removed, too
defs = None
for e in root:
if e.tag == etree.QName(ns['svg'], 'defs'):
defs = e
if (e.tag == etree.QName(ns['svg'], 'g') or
e.tag == etree.QName(ns['svg'], 'metadata') or
e.tag == etree.QName(ns['svg'], 'defs') or
e.tag == etree.QName(ns['sodipodi'], 'namedview') or
(output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
):
root.remove(e)
# template preparation finished
# prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+')
regex_space = re.compile(r'[>]\s+[<]')
# find all groups
layers = tree.xpath('//svg:g', namespaces=ns)
for layer in layers:
i = layer.get('id')
if ( # disregard non-content groups
i.endswith("_") or # manually suppressed with underscore
i.startswith("XMLID") or # Illustrator generated group
i.startswith("g") # Inkscape generated group
):
continue
# create new canvas
output = copy.deepcopy(template)
# copy all shapes into template
canvas = output.getroot()
for e in layer:
canvas.append(e)
# represent template as SVG (binary string)
svg = etree.tostring(output, pretty_print=False)
# poor man's conditional defs insertion
# TODO: extract only referenced defs (filters, gradients, ...)
# TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
if "filter:" in svg.decode('utf-8'):
# it seems there is a filter referenced in the generated SVG, re-insert defs from main document
canvas.insert(0, defs)
# re-generate output
svg = etree.tostring(output, pretty_print=False)
if output_format == 'tw':
# remove unnecessary attributes
# TODO: never generate unnecessary attributes in the first place
svg = svg.decode('utf-8')
svg = regex_xmlns.sub('', svg)
svg = svg.replace('\n', '').replace('\r', '') # print cannot be multi-line
svg = regex_space.sub('><', svg) # remove indentation
svg = svg.replace('svg:', '') # svg namespace was removed
if "Boob" in i: # internal groups are used for scaling
svg = svg.replace('<g ', '<g data-transform="boob" ') # boob art uses the boob scaling
elif "Belly" in i:
svg = svg.replace('<g ', '<g data-transform="belly" ') # belly art uses the belly scaling
elif "Balls" in i:
svg = svg.replace('<g ', '<g data-transform="balls" ') # balls art uses the balls scaling
else:
svg = svg.replace('<g ', '<g data-transform="art" ') # otherwise use default scaling
if not svg.endswith(os.linesep):
svg += os.linesep
svg = svg.encode('utf-8')
# save SVG string to file
svg_split_utils.save(layer, args.prefix, output_directory, output_format, svg)
input_file = sys.argv[1]
output_format = sys.argv[2]
output_directory = sys.argv[3]
if not os.path.exists(output_directory):
os.makedirs(output_directory)
ns = {
'svg' : 'http://www.w3.org/2000/svg',
'inkscape' : 'http://www.inkscape.org/namespaces/inkscape',
'sodipodi':"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
}
tree = etree.parse(input_file)
normalize_svg.fix(tree)
# prepare output template
template = copy.deepcopy(tree)
root = template.getroot()
# remove all svg root attributes except document size
for a in root.attrib:
if (a != "viewBox"):
del root.attrib[a]
# add placeholder for CSS class (needed for workaround for non HTML 5.1 compliant browser)
if output_format == 'tw':
root.attrib["class"] = "'+_art_display_class+'"
# remove all content, including metadata
# for twine output, style definitions are removed, too
defs = None
for e in root:
if (e.tag == etree.QName(ns['svg'], 'defs')):
defs = e
if (e.tag == etree.QName(ns['svg'], 'g') or
e.tag == etree.QName(ns['svg'], 'metadata') or
e.tag == etree.QName(ns['svg'], 'defs') or
e.tag == etree.QName(ns['sodipodi'], 'namedview') or
(output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
):
root.remove(e)
# template preparation finished
# prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+',)
regex_space = re.compile('[>][ ]+[<]',)
# find all groups
layers = tree.xpath('//svg:g',namespaces=ns)
for layer in layers:
i = layer.get('id')
if ( # disregard non-content groups
i.endswith("_") or # manually suppressed with underscore
i.startswith("XMLID") or # Illustrator generated group
i.startswith("g") # Inkscape generated group
):
continue
# create new canvas
output = copy.deepcopy(template)
# copy all shapes into template
canvas = output.getroot()
for e in layer:
canvas.append(e)
# represent template as SVG (binary string)
svg = etree.tostring(output, pretty_print=False)
# poor man's conditional defs insertion
# TODO: extract only referenced defs (filters, gradients, ...)
# TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
if ("filter:" in svg.decode('utf-8')):
# it seems there is a filter referenced in the generated SVG, re-insert defs from main document
canvas.insert(0,defs)
# re-generate output
svg = etree.tostring(output, pretty_print=False)
if (output_format == 'tw'):
# remove unnecessary attributes
# TODO: never generate unnecessary attributes in the first place
svg = svg.decode('utf-8')
svg = regex_xmlns.sub('',svg)
svg = svg.replace(' inkscape:connector-curvature="0"','') # this just saves space
svg = svg.replace('\n','').replace('\r','') # print cannot be multi-line
svg = regex_space.sub('><',svg) # remove indentation
svg = svg.replace('svg:','') # svg namespace was removed
if ("Boob" in i): # internal groups are used for scaling
svg = svg.replace('<g ','<g transform="\'+_artTransformBoob+\'"') # boob art uses the boob scaling
elif ("Belly" in i):
svg = svg.replace('<g ','<g transform="\'+_artTransformBelly+\'"') # belly art uses the belly scaling
else:
svg = svg.replace('<g ','<g transform="\'+_art_transform+\'"') # otherwise use default scaling
svg = svg.encode('utf-8')
# save SVG string to file
i = layer.get('id')
output_path = os.path.join(output_directory,i+'.'+output_format)
with open(output_path, 'wb') as f:
if (output_format == 'svg'):
# Header for normal SVG (XML)
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
elif (output_format == 'tw'):
# Header for SVG in Twine file (SugarCube print statement)
f.write((':: Art_Vector_%s [nobr]\n\n'%(i)).encode("utf-8"))
f.write("<<print '<html>".encode("utf-8"))
f.write(svg)
f.write("</html>' >>".encode("utf-8"))
os.makedirs(output_directory)
for f in args.input_file:
split_file(f)
#!/usr/bin/env python3
'''
"""
Application for splitting groups from one SVG file into separate files
Usage:
......@@ -8,133 +8,169 @@ python3 vector_layer_split.py infile format outdir
Usage Example:
python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/
'''
"""
import lxml.etree as etree
from lxml.etree import XMLParser, parse
import sys
import os
import argparse
import copy
import os
import re
import lxml.etree as etree
import normalize_svg
from lxml.etree import XMLParser, parse
import svg_split_utils
parser = argparse.ArgumentParser(
description='Application for splitting groups from one SVG file into separate files.')
parser.add_argument('-o', '--output', dest='output_dir', required=True,
help='output directory')
parser.add_argument('-f', '--format', dest='output_format',
choices=['svg', 'tw'], default='svg', help='output format.')
parser.add_argument('-p', '--prefix', dest='prefix', default='',
help='Prepend this string to result file names')
parser.add_argument('input_file', metavar='FILENAME', nargs=1,
help='Input SVG file with layers')
input_file = sys.argv[1]
output_format = sys.argv[2]
output_directory = sys.argv[3]
args = parser.parse_args()
output_format = args.output_format
output_directory = args.output_dir
input_file = args.input_file[0]
if not os.path.exists(output_directory):
os.makedirs(output_directory)
os.makedirs(output_directory)
ns = {
'svg': 'http://www.w3.org/2000/svg',
'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
'sodipodi': "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
'svg': 'http://www.w3.org/2000/svg',
'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
'sodipodi': "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
}
p = XMLParser(huge_tree=True)
tree = parse(input_file, parser=p)
#tree = etree.parse(input_file)
normalize_svg.fix(tree)
# strip undesired editor attributes from the tree
etree.strip_attributes(tree, f"{{{ns['inkscape']}}}*", f"{{{ns['sodipodi']}}}*")
# prepare output template
template = copy.deepcopy(tree)
root = template.getroot()
# remove all svg root attributes except document size
for a in root.attrib:
if (a != "viewBox"):
del root.attrib[a]
# add placeholder for CSS class (needed for workaround for non HTML 5.1 compliant browser)
if output_format == 'tw':
root.attrib["class"] = "'+_art_display_class+'"
if a != "viewBox":
del root.attrib[a]
# remove all content, including metadata
# for twine output, style definitions are removed, too
defs = None
for e in root:
if (e.tag == etree.QName(ns['svg'], 'defs')):
defs = e
if (e.tag == etree.QName(ns['svg'], 'g') or
e.tag == etree.QName(ns['svg'], 'metadata') or
e.tag == etree.QName(ns['svg'], 'defs') or
e.tag == etree.QName(ns['sodipodi'], 'namedview') or
(output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
):
root.remove(e)
if e.tag == etree.QName(ns['svg'], 'defs'):
defs = e
if (e.tag == etree.QName(ns['svg'], 'g') or
e.tag == etree.QName(ns['svg'], 'metadata') or
e.tag == etree.QName(ns['svg'], 'defs') or
e.tag == etree.QName(ns['sodipodi'], 'namedview') or
(output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
):
root.remove(e)
# template preparation finished
# prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+', )
regex_space = re.compile('[>][ ]+[<]', )
regex_transformValue = re.compile('(?<=transformVariableName=")[^"]*', )
regex_transformVar = re.compile('transformVariableName="[^"]*"', )
regex_transformAttr = re.compile('transform="[^"]*"', )
# find all groups
regex_xmlns = re.compile(' xmlns[^ ]+')
regex_space = re.compile(r'[>]\s+[<]')
# create dict with all needed definitions
gra_dict = {}
for gra in defs:
if (gra.tag == etree.QName(ns['svg'], 'linearGradient') or
gra.tag == etree.QName(ns['svg'], 'radialGradient') or
gra.tag == etree.QName(ns['svg'], 'clipPath') or
gra.tag == etree.QName(ns['svg'], 'mask') or
gra.tag == etree.QName(ns['svg'], 'filter')):
gra_id = gra.get('id')
gra = etree.tostring(gra, pretty_print=False)
gra = gra.decode('utf-8')
gra_dict[gra_id] = gra
# find all groups
layers = tree.xpath('//svg:g', namespaces=ns)
for layer in layers:
i = layer.get('id')
if ( # disregard non-content groups
i.endswith("_") or # manually suppressed with underscore
i.startswith("XMLID") or # Illustrator generated group
i.startswith("g") # Inkscape generated group
):
continue
# create new canvas
output = copy.deepcopy(template)
# copy all shapes into template
canvas = output.getroot()
for e in layer:
canvas.append(e)
# represent template as SVG (binary string)
svg = etree.tostring(output, pretty_print=False)
# poor man's conditional defs insertion
# TODO: extract only referenced defs (filters, gradients, ...)
# TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
if ("filter:" in svg.decode('utf-8')):
# it seems there is a filter referenced in the generated SVG, re-insert defs from main document
canvas.insert(0, defs)
# re-generate output
svg = etree.tostring(output, pretty_print=False)
elif ("clip-path=" in svg.decode('utf-8')):
# it seems there is a clip path referenced in the generated SVG, re-insert defs from main document
canvas.insert(0, defs)
# re-generate output
svg = etree.tostring(output, pretty_print=False)
if (output_format == 'tw'):
# remove unnecessary attributes
# TODO: never generate unnecessary attributes in the first place
svg = svg.decode('utf-8')
svg = regex_xmlns.sub('', svg)
svg = svg.replace(' inkscape:connector-curvature="0"', '') # this just saves space
svg = svg.replace('\n', '').replace('\r', '') # print cannot be multi-line
svg = regex_space.sub('><', svg) # remove indentation
svg = svg.replace('svg:', '') # svg namespace was removed
transformGroups = regex_transformVar.findall(svg)
if (len(transformGroups) > 0):
svg = regex_transformAttr.sub('', svg)
for transformGroup in transformGroups:
transformValue = regex_transformValue.search(transformGroup)
if (transformValue is not None):
svg = svg.replace(transformGroup, ' transform="\'+' + transformValue.group() + '+\'" ') # internal groups are used for scaling
svg = svg.encode('utf-8')
# save SVG string to file
i = layer.get('id')
output_path = os.path.join(output_directory, i + '.' + output_format)
with open(output_path, 'wb') as f:
if (output_format == 'svg'):
# Header for normal SVG (XML)
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
elif (output_format == 'tw'):
# Header for SVG in Twine file (SugarCube print statement)
f.write((':: Art_Vector_Revamp_%s [nobr]\n\n' % (i)).encode("utf-8"))
f.write("<<print '<html>".encode("utf-8"))
f.write(svg)
f.write("</html>' >>".encode("utf-8"))
i = layer.get('id')
if ( # disregard non-content groups
i.endswith("_") or # manually suppressed with underscore
i.startswith("XMLID") or # Illustrator generated group
i.startswith("g") # Inkscape generated group
):
continue
# create new canvas
output = copy.deepcopy(template)
# copy all shapes into template
canvas = output.getroot()
for e in layer:
canvas.append(e)
# represent template as SVG (binary string)
svg = etree.tostring(output, pretty_print=False)
# replace <use> with the referenced object
# this is a very poor way to do it. A more elegant way would be to insert
# referenced object in the <def> part of the file, but then the colors of
# objects with a class definition will not be changed.
# As a substitute the <use> is overwritten with the object.
# THIS WILL REMOVE ANY MODIFICATIONS (movement, clip-paths, etc.)
# To prevent this the cloned object should be put into a group, and changes
# made to the group.
# TODO: If colors can be changed based on class in defs - put reference in defs instead
if "<svg:use" in svg.decode('utf-8'):
# get a reference map
output_map = {c: p for p in canvas.iter() for c in p}
# find use elements
for elem in list(canvas.xpath('//svg:use', namespaces=ns)):
# get referenced id
def_id = elem.get('{http://www.w3.org/1999/xlink}href')
def_id = def_id[1:]
# loop through all layers to find the referenced element
def_orig = None
for def_l in layers:
for e in (e for e in def_l if e.get('id') == def_id):
def_orig = copy.deepcopy(e)
if def_orig is not None:
break
parent_index = list(output_map).index(elem)
output_map[elem].remove(elem)
output_map[elem].insert(parent_index, def_orig)
# Update svg
svg = etree.tostring(output, pretty_print=False)
# Conditional defs insertion - only inserts the needed defs
svg_dec = svg.decode('utf-8')
if any(x in svg_dec for x in ["filter", "Gradient", "mask=", "clip-path="]):
moreDefsNeeded = True
gra_str = '<defs>'
while moreDefsNeeded:
moreDefsNeeded = False
s = svg.decode('utf-8')
for key in gra_dict.keys():
if ('#' + key in s or '#' + key in gra_str) and 'id="' + key not in gra_str:
moreDefsNeeded = True
gra_str += gra_dict[key]
gra_str += '</defs>'
canvas.insert(0, etree.fromstring(gra_str))
# re-generate output
svg = etree.tostring(output, pretty_print=False)
if output_format == 'tw':
# remove unnecessary attributes
# TODO: never generate unnecessary attributes in the first place
svg = svg.decode('utf-8')
svg = regex_xmlns.sub('', svg)
svg = svg.replace('\n', '').replace('\r', '') # print cannot be multi-line
svg = regex_space.sub('><', svg) # remove indentation
svg = svg.replace('svg:', '') # svg namespace was removed
if not svg.endswith(os.linesep):
svg += os.linesep
svg = svg.encode('utf-8')
# save SVG string to file
svg_split_utils.save(layer, args.prefix, output_directory, output_format, svg)