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
Select Git revision

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
  • Liberator/pregmod-ai-clothing
381 results
Select Git revision
Show changes
Showing
with 33811 additions and 235 deletions
#include "fchost_storage_js.h"
#include <memory>
// static storage objects (not threadsafe, probably doesn't matter, since they'll execute within a JS context)
static std::unique_ptr<FCHostPersistentStorage> persist;
static std::unique_ptr<FCHostSessionStorage> session;
// storage handler js hook implementations
#define REGJSFUNC(x) { \
CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction(x, handler); \
object->SetValue(x, func, V8_PROPERTY_ATTRIBUTE_NONE); }
static void AttachStorageFunctions(CefRefPtr<CefV8Value> object, CefRefPtr<CefV8Handler> handler)
{
REGJSFUNC("size");
REGJSFUNC("keys");
REGJSFUNC("has");
REGJSFUNC("get");
REGJSFUNC("set");
REGJSFUNC("remove");
REGJSFUNC("clear");
}
void FCHostStorageRegister(const std::filesystem::path& persistPath, CefRefPtr<CefV8Value> object) {
// New context, reset everything (don't try to run more than one app at once!)
if (persist) persist.reset(NULL);
if (session) session.reset(NULL);
persist = std::make_unique<FCHostPersistentStorage>(persistPath);
session = std::make_unique<FCHostSessionStorage>();
CefRefPtr<CefV8Handler> sess_handler = new FCHostStorageHandler(false);
CefRefPtr<CefV8Value> obj_sess = CefV8Value::CreateObject(nullptr, nullptr);
object->SetValue("FCHostSession", obj_sess, V8_PROPERTY_ATTRIBUTE_NONE);
AttachStorageFunctions(obj_sess, sess_handler);
CefRefPtr<CefV8Handler> pers_handler = new FCHostStorageHandler(true);
CefRefPtr<CefV8Value> obj_pers = CefV8Value::CreateObject(nullptr, nullptr);
object->SetValue("FCHostPersistent", obj_pers, V8_PROPERTY_ATTRIBUTE_NONE);
AttachStorageFunctions(obj_pers, pers_handler);
}
// individual execution objects for session/persistent storage
bool FCHostStorageHandler::Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception)
{
// alias pointer only, no ownership
FCHostSessionStorage* storage = persistent ? persist.get() : session.get();
if (name == "size") {
// no arguments
retval = CefV8Value::CreateInt(static_cast<int32_t>(storage->size()));
return true;
}
else if (name == "keys") {
// no arguments
retval = storage->keys();
return true;
}
else if (name == "has") {
// one string argument
if (arguments.size() < 1 || !arguments[0]->IsString()) return false;
retval = CefV8Value::CreateBool(storage->has(arguments[0]->GetStringValue()));
return true;
}
else if (name == "get") {
// one string argument
if (arguments.size() < 1 || !arguments[0]->IsString()) return false;
retval = storage->get(arguments[0]->GetStringValue());
return true;
}
else if (name == "set") {
// two arguments - one string, one "whatever"
if (arguments.size() < 2 || !arguments[0]->IsString()) return false;
storage->set(arguments[0]->GetStringValue(), arguments[1]);
retval = CefV8Value::CreateBool(true);
return true;
}
else if (name == "remove") {
// one string argument
if (arguments.size() < 1 || !arguments[0]->IsString()) return false;
retval = CefV8Value::CreateBool(storage->remove(arguments[0]->GetStringValue()));
return true;
}
else if (name == "clear") {
// no arguments
storage->clear();
return true;
}
return false;
}
#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
<!-- 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
* WARNING - JoiPlay users need to export any saves they care about (using the Save to Clipboard option) before updating from alpha.33 or earlier. Any saves that aren't exported will be lost!
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 police force
Bugs:
-sometimes troop counts breaks
-sometimes rebel numbers have fractionary parts
\ No newline at end of file
<!-- cSpell:ignore faraen, eart, lxml, XMLID -->
# How to vector art # How to vector art
Please read the whole document before starting to work. Please read the whole document before starting to work.
...@@ -7,67 +9,117 @@ Note: This does not actually describe how to be an artist. ...@@ -7,67 +9,117 @@ Note: This does not actually describe how to be an artist.
## TL;DR ## TL;DR
killall inkscape killall inkscape
artTools/vector_layer_split.py artTools/vector_source.svg tw src/art/vector/layers/ artTools/vector_layer_split.py artTools/vector_source.svg tw src/art/vector/layers/
compile compile
python3 artTools/normalize_svg.py artTools/vector_source.svg
git commit -a
## 1. Be an artist ## 1. Be an artist
Make changes to the vector_source.svg. Make changes to the vector_source.svg.
Inkscape was thoroughly tested. Inkscape was thoroughly tested.
Adobe Illustrator might work decently, too. Adobe Illustrator might work decently, too.
## 2. Respect the structure ## 2. Respect the structure
While editing, keep the Layers in mind. While editing, keep the Layers in mind.
* In Inkscape, Layers are special "Inkscape groups". * In Inkscape, Layers are special "Inkscape groups".
* In Illustrator, Layers are groups with user-definded IDs. * In Illustrator, Layers are groups with user-defined IDs.
* All Layers should have an ID that is globally unique * All Layers should have an ID that is globally unique
(not just within their subtree). (not just within their subtree).
* Please use anonymous groups only for the ease of editing. Remove all "helper" groups before finally saving the file. * Please use anonymous groups only for the ease of editing. Remove all "helper" groups before finally saving the file.
* Anonymous groups can be used for continous scaling (of e.g. boobs). * Anonymous groups can be used for continuous scaling (of e.g. boobs).
* Every asset that should go into a separate file needs to be in a labelled group * Every asset that should go into a separate file needs to be in a labelled group
(even if that is a group with only one shape). (even if that is a group with only one shape).
* There are some globally available styles defined as CSS classes (e.g. skin, hair). * There are some globally available styles defined as CSS classes (e.g. skin, hair).
Use them if your asset should be changed upon display. Use them if your asset should be changed upon display.
Do not set the style directly in the object. Do not set the style directly in the object.
* You can use Inkscape to define and add classes to paths.
## 3. Fix the document (before commiting, Inkscape only) 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'.
Inkscape shows weird behaviour in some regards. Add a new field to the path and name it 'class'.
If you use Inkscape, close the document and run Make its value one of the values defined in the below section.
python3 inkscape_svg_fixup.py vector_source.svg **MAINTAIN THESE CLASS DEFINITIONS BY MANUALLY EDITING YOUR WORKING SVG FILE**
before continuing. Open the file in Inkscape and save it again. <style
You need to make a minor change, as Inkscape will not save a unchanged file type="text/css"
(move the notes back and forth or something). The fixup does not produce id="style">
the same linebreaks as Inkscape or Illustrator and git will go mad because /* please maintain these definitions manually */
the whole seems to have changed. .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)
**THIS IS IMPORTANT**
The various editors out there (Inkscape, Illustrator) may use different variants of formatting the SVG XML.
Use
python3 normalize_svg.py vector_source.svg
before committing to normalize the format so git will not freak out about the changed indentation.
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: What it does:
* Adobe Illustrator uses group IDs as layer labels. * Formats the SVG XML according to Pythons lxml module, regardless of editor.
* Adobe Illustrator uses group IDs as layer labels.
Inkscape however uses layer labels and a separate, auto-generated group ID. Inkscape however uses layer labels and a separate, auto-generated group ID.
inkscape_svg_fixup.py overwrites the group ID with the Inkscape layer label normalize_svg.py overwrites the group ID with the Inkscape layer label
so they are synchronised with Inkscape layer labels. so they are synchronized with Inkscape layer labels.
* Inkscape copies the global style definition into the object *every time* * Inkscape copies the global style definition into the object *every time*
the object is edited. If an object references a CSS class AND at the same time the object is edited. If an object references a CSS class AND at the same time
has a local style, the local style is removed has a local style, the local style is removed
so global dynamic styling is possible later on. so global dynamic styling is possible later on.
Note: Behaviour of Adobe Illustrator is untested. Note: Behavior of Adobe Illustrator is untested.
## 4. Split the layers ## 4. Split the layers
Execute For NoX original, deepmurk extensions, execute
python3 vector_layer_split.py vector_deepmurk_primary.svg tw ../src/art/vector/layers/
For faraen revamped art (based on NoX original)
python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/ python3 vector_revamp_layer_split.py vector_revamp_source.svg tw ../src/art/vector_revamp/layers/
. This application reads all groups in `vector_source.svg`. . This application reads all groups in `vector_source.svg`.
Each group is stored in a separate file in the target directory `/src/art/vector/layers/`. Each group is stored in a separate file in the target directory `/src/art/vector/layers/`.
The group ID sets the file name. Therefore, the group ID **must not** contain spaces or any other weird characters. The group ID sets the file name. Therefore, the group ID **must not** contain spaces or any other weird characters.
Also consider: Also consider:
...@@ -77,14 +129,14 @@ Also consider: ...@@ -77,14 +129,14 @@ Also consider:
* The target directory is not emptied. If a file is no longer needed, you should remove it manually. * The target directory is not emptied. If a file is no longer needed, you should remove it manually.
* This procedure removes global definitions. This means, SVG filters are currently not supported. * This procedure removes global definitions. This means, SVG filters are currently not supported.
Available output formats are `svg` and `tw`. Available output formats are `svg` and `tw`.
`svg` output exists for debug reasons. `svg` output exists for debug reasons.
`tw` embeds the SVG data into Twine files, but removes the global style definitions so they can be set during display. `tw` embeds the SVG data into Twine files, but removes the global style definitions so they can be set during display.
## 5. Edit the code ## 5. Edit the code
`/src/art/` contains Twine code which shows the assets in the story. `/src/art/` contains Twine code which shows the assets in the story.
There are many helpful comments in `/src/art/artWidgets.tw`. There are many helpful comments in `/src/art/artWidgets.tw`.
The code also generates the previously removed global style definitions on the fly and per display. The code also generates the previously removed global style definitions on the fly and per display.
## 6. Compile the story ## 6. Compile the story
......
Apron
AttractiveLingerie
AttractiveLingerieForAPregnantWoman
BallGown
Battledress
Blouse
BodyOil
Bunny
Chains
ChattelHabit
Cheerleader
ClubslutNetting
ComfortableBodysuit
Conservative
Cutoffs
Cybersuit
FallenNunsHabit
FuckdollSuit
HalterTopDress
HaremGauze
Hijab
Huipil
Kimono
LatexCatsuit
Leotard
LongQipao
MaternityDress
MilitaryUniform
MiniDress
Monokini
NiceBusinessAttire
NiceMaid
NiceNurse
No
PenitentNunsHabit
RedArmyUniform
RestrictiveLatex
ScalemailBikini
SchoolgirlUniform
SchutzstaffelUniform
ShibariRopes
SlaveGown
Slutty
SluttyBusinessAttire
SluttyJewelry
SluttyMaid
SluttyNurse
SluttyQipao
SluttySchutzstaffelUniform
Spats
StretchPants
StringBikini
Succubus
Toga
UncomfortableStraps
Western
#!/bin/bash
# This script reads all possible values of $slave.clothes as mentioned by the documentation.
# This script uses the actual implementation of the JS clothing2artSuffix function as defined in src/art/vector/Helper_Functions.tw
# This script outputs suffixes to be used in the SVG naming scheme
# This script is meant to be executed at the project root directory.
# This script depends on bash, grep, sed, paste and nodejs (so best executed on Linux, I guess)
# grep -Porh '(?<=\.clothes = )"[^"]+"' src/ | sort | uniq # searches sources for all clothes strings actually used in the game, even undocumented ones
(
echo 'var window = {};'
grep -v '^:' src/art/vector/Helper_Functions.tw
echo -n 'Array('
sed '/^clothes:/,/:/!d' "slave variables documentation - Pregmod.txt" | grep '"' | paste -sd,
echo ').forEach(v => {console.log(window.clothing2artSuffix(v));});'
) | nodejs | sort
#!/usr/bin/env python3
'''
Application for "fixing" SVGs edited with Inkscape
These problems are addressed:
* Inkscape notoriously copies class styles into the object definitions.
https://bugs.launchpad.net/inkscape/+bug/167937
* Inkscape uses labels on layers. Layers are basically named groups.
Inkscape does not sync the group id with the layer label.
Usage Example:
python3 inkscape_svg_fixup.py vector_source.svg
Note:
The output of lxml differs greatly from standard SVG indentation.
Please open and save the file in Inkscape before committing.
'''
import lxml.etree as etree
import sys
def fix(tree):
# 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)
# 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)
# remove all offending style attributes
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)
#!/usr/bin/env python3
"""
Application for "normalizing" SVGs
These problems are addressed:
* Inkscape notoriously copies class styles into the object definitions.
https://bugs.launchpad.net/inkscape/+bug/167937
* Inkscape uses labels on layers. Layers are basically named groups.
Inkscape does not sync the group id with the layer label.
Usage Example:
python3 inkscape_svg_fixup.py vector_source.svg
"""
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'
}
# 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
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
# 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
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)
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 #!/usr/bin/env python3
''' """
Application for procedural content adaption Application for procedural content adaption
*THIS IS VERY EXPERIMENTAL* *THIS IS VERY EXPERIMENTAL*
Contains a very poor man's implementation of spline mesh warping. Contains a very poor man's implementation of spline mesh warping.
...@@ -23,15 +23,16 @@ python3 vector_clothing_replicator.py infile clothing bodypart destinationfile ...@@ -23,15 +23,16 @@ python3 vector_clothing_replicator.py infile clothing bodypart destinationfile
Usage Example: Usage Example:
python3 vector_clothing_replicator.py vector_source.svg Straps Boob vector_destination.svg 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 python3 vector_clothing_replicator.py vector_source.svg Straps Torso vector_destination.svg
''' """
from svg.path import parse_path
import copy import copy
import lxml.etree as etree
import sys import sys
import lxml.etree as etree
from svg.path import parse_path
REFERENCE_PATH_SAMPLES = 200 REFERENCE_PATH_SAMPLES = 200
EMBED_REPLICATIONS = True # wether 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] input_file = sys.argv[1]
clothing = sys.argv[2] clothing = sys.argv[2]
...@@ -39,121 +40,127 @@ bodypart = sys.argv[3] ...@@ -39,121 +40,127 @@ bodypart = sys.argv[3]
output_file_embed = sys.argv[4] output_file_embed = sys.argv[4]
# TODO: make these configurable # TODO: make these configurable
output_file_pattern = '%s_%s_%s.svg' #bodypart, target_id, clothing output_file_pattern = '%s_%s_%s.svg' # bodypart, target_id, clothing
if ('Torso' == bodypart): 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_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_container = '//svg:g[@id="Torso_Outfit_%s_"]' % (clothing)
xpath_outfit = '//svg:g[@id="Torso_Outfit_%s_%s"]'%(clothing,'%s') xpath_outfit = '//svg:g[@id="Torso_Outfit_%s_%s"]' % (clothing, '%s')
target_ids = "Unnatural,Hourglass,Normal".split(",") target_ids = "Unnatural,Hourglass,Normal".split(",")
reference_id = "Hourglass" reference_id = "Hourglass"
else: 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) 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) canvas = copy.deepcopy(tree)
for e in canvas.xpath('./svg:g',namespaces=ns)+canvas.xpath('./svg:path',namespaces=ns): 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" # TODO: this should be "remove all objects, preserve document properties"
e.getparent().remove(e) e.getparent().remove(e)
def get_points(xpath_shape): def get_points(xpath_shape):
''' """
This funciton extracts reference paths by the given xpath selector. This function extracts reference paths by the given xpath selector.
Each path is used to sample a fixed number of points. Each path is used to sample a fixed number of points.
''' """
paths_data = tree.xpath(xpath_shape,namespaces=ns) paths_data = tree.xpath(xpath_shape, namespaces=ns)
points = [] points = []
path_length = None path_length = None
for path_data in paths_data: for path_data in paths_data:
p = parse_path(path_data) p = parse_path(path_data)
points += [ points += [
p.point(1.0/float(REFERENCE_PATH_SAMPLES)*i) p.point(1.0 / float(REFERENCE_PATH_SAMPLES) * i)
for i in range(REFERENCE_PATH_SAMPLES) for i in range(REFERENCE_PATH_SAMPLES)
] ]
if (not points): if not points:
raise RuntimeError( raise RuntimeError(
'No paths for reference points found by selector "%s".'%(xpath_shape) 'No paths for reference points found by selector "%s".' % xpath_shape
) )
return points return points
def point_movement(point, reference_points, target_points): def point_movement(point, reference_points, target_points):
''' """
For a given point, finds the nearest point in the reference path. For a given point, finds the nearest point in the reference path.
Gives distance vector from the nearest reference point to the Gives distance vector from the nearest reference point to the
respective target reference point. respective target reference point.
''' """
distances = [abs(point-reference_point) for reference_point in reference_points] distances = [abs(point - reference_point) for reference_point in reference_points]
min_ref_dist_idx = min(enumerate(distances), key=lambda x:x[1])[0] 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] movement = target_points[min_ref_dist_idx] - reference_points[min_ref_dist_idx]
return movement return movement
reference_points = get_points(xpath_shape%(reference_id))
container = tree.xpath(xpath_outfit_container,namespaces=ns) reference_points = get_points(xpath_shape % reference_id)
if (len(container) != 1): container = tree.xpath(xpath_outfit_container, namespaces=ns)
raise RuntimeError('Outfit container selector "%s" does not yield exactly one layer.'%(xpath_outfit_container)) if len(container) != 1:
raise RuntimeError('Outfit container selector "%s" does not yield exactly one layer.' % xpath_outfit_container)
container = container[0] container = container[0]
outfit_source = container.xpath(xpath_outfit%(reference_id),namespaces=ns) outfit_source = container.xpath(xpath_outfit % reference_id, namespaces=ns)
if (len(outfit_source) != 1): 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)) 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] outfit_source = outfit_source[0]
for target_id in target_ids: for target_id in target_ids:
print( print(
'Generating variant "%s" of clothing "%s" for bodypart "%s"...'% 'Generating variant "%s" of clothing "%s" for bodypart "%s"...' %
(target_id, clothing, bodypart) (target_id, clothing, bodypart)
) )
outfit = copy.deepcopy(outfit_source) outfit = copy.deepcopy(outfit_source)
paths = outfit.xpath('./svg:path',namespaces=ns) paths = outfit.xpath('./svg:path', namespaces=ns)
if target_id == reference_id: if target_id == reference_id:
print("This is the source variant. Skipping...") print("This is the source variant. Skipping...")
else: else:
layerid = outfit.get('id').replace('_%s'%(reference_id),'_%s'%(target_id)) layerid = outfit.get('id').replace('_%s' % reference_id, '_%s' % target_id)
outfit.set('id', layerid) outfit.set('id', layerid)
outfit.set(etree.QName('http://www.inkscape.org/namespaces/inkscape', 'label'), layerid) # for the Inkscape-users outfit.set(etree.QName('http://www.inkscape.org/namespaces/inkscape', 'label'),
target_points = get_points(xpath_shape%(target_id)) layerid) # for the Inkscape-users
if (len(reference_points) != len(target_points)): target_points = get_points(xpath_shape % target_id)
raise RuntimeError( if len(reference_points) != len(target_points):
('Different amounts of sampled points in reference "%s" and target "%s" paths. '+ raise RuntimeError(
'Selector "%s" probably matches different number of paths in the two layers.')% ('Different amounts of sampled points in reference "%s" and target "%s" paths. ' +
(reference_id, target_id, xpath_shape) '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") for path in paths:
p = parse_path(path_data) path_data = path.get("d")
for segment in p: p = parse_path(path_data)
original_distance = abs(segment.end-segment.start) for segment in p:
start_movement = point_movement(segment.start, reference_points, target_points) original_distance = abs(segment.end - segment.start)
segment.start += start_movement start_movement = point_movement(segment.start, reference_points, target_points)
end_movement = point_movement(segment.end, reference_points, target_points) segment.start += start_movement
segment.end += end_movement end_movement = point_movement(segment.end, reference_points, target_points)
distance = abs(segment.end-segment.start) segment.end += end_movement
try: distance = abs(segment.end - segment.start)
# enhance position of CubicBezier control points try:
# amplification is relative to the distance gained by movement # enhance position of CubicBezier control points
segment.control1 += start_movement # amplification is relative to the distance gained by movement
segment.control1 += (segment.control1-segment.start)*(distance/original_distance-1.0) segment.control1 += start_movement
segment.control2 += end_movement segment.control1 += (segment.control1 - segment.start) * (distance / original_distance - 1.0)
segment.control2 += (segment.control2-segment.end)*(distance/original_distance-1.0) segment.control2 += end_movement
except AttributeError as ae: segment.control2 += (segment.control2 - segment.end) * (distance / original_distance - 1.0)
# segment is not a CubicBezier except AttributeError as ae:
pass # segment is not a CubicBezier
path.set("d", p.d()) pass
if EMBED_REPLICATIONS: path.set("d", p.d())
container.append(outfit) if EMBED_REPLICATIONS:
if not EMBED_REPLICATIONS: container.append(outfit)
container = copy.deepcopy(canvas).xpath('.',namespaces=ns)[0] if not EMBED_REPLICATIONS:
container.append(outfit) container = copy.deepcopy(canvas).xpath('.', namespaces=ns)[0]
container.append(outfit)
if not EMBED_REPLICATIONS:
svg = etree.tostring(container, pretty_print=True) if not EMBED_REPLICATIONS:
with open((output_file_pattern%(bodypart, target_id, clothing)).lower(), 'wb') as f: svg = etree.tostring(container, pretty_print=True)
f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8")) with open((output_file_pattern % (bodypart, target_id, clothing)).lower(), 'wb') as f:
f.write(svg) f.write('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg)
if EMBED_REPLICATIONS: if EMBED_REPLICATIONS:
svg = etree.tostring(tree, pretty_print=True) svg = etree.tostring(tree, pretty_print=True)
with open(output_file_embed, 'wb') as f: 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('<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n'.encode("utf-8"))
f.write(svg) f.write(svg)
Source diff could not be displayed: it is too large. Options to address this: view the blob.
Source diff could not be displayed: it is too large. Options to address this: view the blob.
Source diff could not be displayed: it is too large. Options to address this: view the blob.