Compare commits

...

51 Commits

Author SHA1 Message Date
Saveliy Skresanov
6ce8e10adb Add a limit on the number of SPARK, LFLARE and FLARE effects drawn in one frame. 2024-04-22 22:49:00 +07:00
jacob1
47384c5572
ICE contains arbitrary elements in ctype as well and should track this in saves 2024-04-21 00:11:55 -04:00
jacob1
de345a85a1
Fix "missing custom elements" warnings when loading saves with RSST/RSSS; mark SNOW as carrying ctype as well 2024-04-19 23:31:20 -04:00
Tamás Bálint Misius
6de252eb34
Check starcatcher credentials early
It's infuriating when the entire workflow fails at the publish stage because Discord pretends to have markdown.
2024-04-14 20:36:05 +02:00
Tamás Bálint Misius
b39f3c7f55
Disable --backend=vs build
Someone please give microsoft engineers a lecture on how to use computers.
2024-04-14 20:34:34 +02:00
Tamás Bálint Misius
58c0ab4747
Fix more &vec[0] problems 2024-04-13 22:32:43 +02:00
Tamás Bálint Misius
73daf67c34
Add elem.PROP_BLACK back for compat
Of course it still does nothing.
2024-04-13 19:08:19 +02:00
Simon Robertshaw
3edb8c4233 Clamp window position/size to graphics surface 2024-04-12 18:14:19 +01:00
Saveliy Skresanov
351dc6ec87 Make dropdowns near the top of the screen go down. 2024-04-12 23:55:34 +07:00
Saveliy Skresanov
0cfb91ce86 Add day/week/month/year selector in the search. 2024-04-12 22:50:55 +07:00
Saveliy Skresanov
efeac4fd8a Make "being transparent to photons" a property PROP_PHOTPASS. 2024-04-10 21:48:51 +07:00
Simon Robertshaw
36619df4f4 Revert "Remove Renderer field from GraphicsFunContext"
This reverts commit 9f02999947 & 2c55a8a9d9
2024-04-07 16:31:10 +01:00
Simon Robertshaw
9f02999947 Remove Renderer field from GraphicsFunContext
- Only decorationsEnable & blackDecorations were ever read, graphics update functions shouldn't really be aware of the renderer.
 - `sim` should ideally not be there either, but `luaGraphicsWrapper` has more going on to sort out
 - Re-format previous commit with tabs instead of spaces
2024-04-07 10:00:08 +01:00
Simon Robertshaw
2c55a8a9d9 Remove Renderer field from GraphicsFunContext
- Only decorationsEnable & blackDecorations were ever read, graphics update functions shouldn't really be aware of the renderer.
 - `sim` should ideally not be there either, but `luaGraphicsWrapper` has more going on to sort out
2024-04-07 00:17:48 +01:00
Saveliy Skresanov
1a0eb73ea0 Use create_part in resist reactions, and set CarriesCtypeIn for RSSS. 2024-04-04 22:20:14 +07:00
Saveliy Skresanov
f8873debc6 Fix RSST/RSSS not respecting the CarriesCtypeIn field. Reset GEL's tmp field when it turns into RSST. 2024-04-04 22:10:58 +07:00
Tamás Bálint Misius
51f714de0f
Stop scrolling in ScrollPanels on mousedown
The goal was to let finger flicks that didn't qualify as panning commands cancel momentum scrolling. The final effect is that any click does, which is fine.
2024-04-03 13:40:20 +02:00
jacob1
e371d6345b
Update build numbers (v98.2.365) 2024-04-01 19:54:39 -04:00
jacob1
e55fc8703a
The squirrel infestation problem is mostly resolved
This reverts commits 5e8a28b946, e8c24e7e23, e8c24e7e23
2024-04-01 19:45:14 -04:00
jacob1
40e2e4a62a
Fix text cutoff issues in save title and search error message
Save title can now overlap with the vote buttons again. It doesn't look great, but reorganizing the preview view is a task for a later day. Save titles this long are rare.
2024-04-01 12:57:19 -04:00
jacob1
e9fdb254af
Update version numbers 2024-03-31 19:48:12 -04:00
jacob1
5e8a28b946
Option to set the max amount of sounds that will play at once, between 0-999 2024-03-31 19:42:28 -04:00
jacob1
a53595ce68
coolcats.patch (patch courtesy of Simon) 2024-03-31 11:20:08 -04:00
catsoften
e8c24e7e23
Add squir... sound. Add sound 2024-03-30 22:33:13 -04:00
jacob1
7edc413cdc
Update build number 2024-03-26 20:53:34 -04:00
Tamás Bálint Misius
53b9b0e286
Fix gravity zones not being drawn in some cases
Namely, gravity zones would be rendered when the wrong wall (i.e. not Gravity wall) was selected if custom tools were present in the SC_WALL menu section. This was because their rendering was tied to a tool index into the SC_WALL menu section, rather than to a tool identifier.
2024-03-25 20:54:45 +01:00
Tamás Bálint Misius
99cd354a16
Fix dialuges looping infinitely in some cases
Namely, when no completion callback is specified, they use themselves, of all things, as their completion callback, because lua_gettop returns 0 and somehow Lua is ok with that stack index and thinks it refers to the function being executed.
2024-03-25 20:49:00 +01:00
Tamás Bálint Misius
d56e8387cf
Fix crash upon selecting a stamp too quickly
SaveButton relies on the SaveFile/SaveInfo passed to it at construction being alive until its destruction, or at least while it's being ticked (while its Tick is being called). While SaveButtons are owned by views, SaveFiles/SaveInfos associated with them are owned by models, so models need to immediately notify views (their observers) of changes made to SaveFiles/SaveInfos, or otherwise prevent views (and thus SaveButtons) from accessing stale SaveFile/SaveInfo pointers.

The online save browser model (SearchModel) correctly notifies its observer (SearchView) about changes to its list of SaveInfos. The local save browser (FileBrowserActivity) is not MVC and simply either fully exits when its SaveFile list changes, or it cleans up its SaveButtons beforehand. The stamp browser model (LocalBrowerModel), however, would make changes to its SaveFile list without notifying its observer (LocalBrowserView) when selecting a stamp, so the latter might tick its SaveButtons after SaveFiles associated with them had already been cleaned up. This commit adds the missing notification.

The crash would manifest when the SaveFile associated with a stamp was accessed for the GameSave it owned to be sent off for rendering with ThumbnailRendererTask.
2024-03-24 12:48:15 +01:00
Tamás Bálint Misius
c1c1daa9e5
Allow exporting Lua symbols
Which optionally enables loading Lua shared modules from within even static TPT. Not that anyone actually needs this.

This currently can't work on Windows because DLLs there import symbols by [module name, symbol name] rather than just symbol name. One could in theory export Lua symbols from TPT (I don't know the exact MSVC hack this would require, .def files?) and place a lua51.dll next to the executable that just re-exports them, see https://learn.microsoft.com/en-us/cpp/build/reference/exports?view=msvc-170 , but I've yet to try this.
2024-03-24 08:37:38 +01:00
Tamás Bálint Misius
bfdfebc1d5
Remove unused MD5 code 2024-03-24 08:27:06 +01:00
jacob1
502df57cae
Fix local saves being sorted Z-A instead of A-Z 2024-03-23 23:41:41 -04:00
jacob1
a0ba5f5398
Fix some label cutoff and component overlap bugs
Profile Viewer: a few "Not Provided" labels were cutoff. The score labels overlapped with the scrollbar and prevented clicking on it.
Save Preview: Views label cutoff on saves with over 10M views (aka only id:2198). authorDate label cutoff on saves with really long usernames. This one I "fixed" by making it overlap again like it used to. It only affects a few 2nd and 3rd page saves.
Options UI: Fix ambient air temp label overlapping with textbox. Fix all checkbox secondary desc labels overlapping with checkbox itself. I "fixed" this by adding the checkbox after the label so that clicks take priority, rather than actually fixing the overlap.
2024-03-23 17:26:34 -04:00
jacob1
178519dbb0
Call ren->clearScreen() in renderer if save is invalid, fixes pixel garbage appearing in final thumbnail 2024-03-23 16:10:46 -04:00
Tamás Bálint Misius
bb471e63e1
Fix panels forwarding clicks from anywhere
Very similar to ab28f93753. Broken by 69e0a8b0aa where I added an extra MouseDownInside check to the OnMouseDown (used to be OnMouseClick) of every component except that of sliders AND apparently panels, great.
2024-03-18 18:05:28 +01:00
Tamás Bálint Misius
4b866c409a
Move unlisted stamps to the back
Rather than to the front. It's nice to immediately see stamps that you haven't seen in years but it's even nicer if your most recently used stamps stay easily accessible.
2024-03-18 12:25:14 +01:00
jacob1
3fb356e2a9
Fix renderer crashing due to SimulationData not being instantiated early enough 2024-03-17 23:05:31 -04:00
jacob1
8452d96bf6
Fix update prompt showing up when no update is available if logged out and using the beta channel 2024-03-17 18:16:53 -04:00
jacob1
eef8943a3c
Fix large screen prompt not showing on first run
desktopWidth/desktopHeight were initialized only at the end of SDLOpen, after we checked if larger scales were supported. Need to initialize the window first, then recreate the window after if the scale can be larger. The window won't be centered, but otherwise it works.
2024-03-17 18:15:54 -04:00
jacob1
5160f4ad33
Update version numbers and README 2024-03-17 16:29:14 -04:00
jacob1
7870ec56a3
Limit saves with ETRD with .tmp or .tmp2 set to 98.0 2024-03-17 16:13:56 -04:00
jacob1
16f50b808d
Add limit onto various textboxes, now that textbox width no longer artificially limits it
Save Name is limited to 50 characters by the db column, so stop it there
Ambient Air Temp glitches out when it's too long, so limit to 9
Limit profile location to 40 to prevent spam / abuse
Limit tags to 16 because that's the max the server accepts
2024-03-17 15:14:22 -04:00
jacob1
aa8ee76fbb
Saves with RSST and RSSS are now restricted to 98.0 2024-03-17 14:32:28 -04:00
jacob1
228d559ccd
Replace b_strip with strip in github actions script 2024-03-15 22:42:27 -04:00
jacob1
758d34c4eb
Snapshot 361 2024-03-15 22:12:26 -04:00
jacob1
596e6cece1
Add DECOSPACE_ constants 2024-03-13 21:22:12 -04:00
jacob1
3a3a8c1cc3
Fix save history button not working except on your own saves 2024-03-06 23:11:00 -05:00
Tamás Bálint Misius
f47d0a9828
Make emscripten snapshots use starcatcher as the backend 2024-03-06 22:06:19 +01:00
Tamás Bálint Misius
7cd684ccfe
Unhardcode powdertoy.co.uk from most places
Namely, everywhere except in the network part (first line) of the intro text, since that should be customized more extensively by a client+server developer.
2024-03-06 22:03:51 +01:00
Tamás Bálint Misius
82bcb0ef9e
Fix element buttons missing from element search
Visible since c3cd4f1691, which made element search scrollable.

The last row of buttons was only shown if it had exactly as many buttons as many fit.
2024-03-05 16:10:07 +01:00
Tamás Bálint Misius
c85ebe4a0a
Fix tooltips in element search
Visible since c3cd4f1691, which made element search scrollable.

They would behave as if the scroll panel was never actually scrolled, i.e. tooltips might be shown that belonged to buttons far outside the visible part of the panel's inner area. This was because Panel invoked the OnMouseHover of its children wrong, without taking scrolling into account.
2024-03-05 16:09:34 +01:00
Tamás Bálint Misius
ab28f93753
Fix sliders accepting clicks from anywhere
Broken by 69e0a8b0aa where I added an extra MouseDownInside check to the OnMouseDown (used to be OnMouseClick) of every component except that of sliders.
2024-03-03 19:35:12 +01:00
79 changed files with 414 additions and 392 deletions

6
.github/build.sh vendored
View File

@ -199,7 +199,7 @@ meson_configure+=$'\t'-Dapp_exe=$APP_EXE
meson_configure+=$'\t'-Dapp_id=$APP_ID
meson_configure+=$'\t'-Dapp_data=$APP_DATA
meson_configure+=$'\t'-Dapp_vendor=$APP_VENDOR
meson_configure+=$'\t'-Db_strip=false
meson_configure+=$'\t'-Dstrip=false
meson_configure+=$'\t'-Db_staticpic=false
meson_configure+=$'\t'-Dmod_id=$MOD_ID
case $BSH_HOST_ARCH-$BSH_HOST_PLATFORM-$BSH_HOST_LIBC-$BSH_DEBUG_RELEASE in
@ -290,6 +290,10 @@ if [[ $RELEASE_TYPE == snapshot ]] && [[ $MOD_ID != 0 ]]; then
fi
if [[ $RELEASE_TYPE == snapshot ]] || [[ $MOD_ID != 0 ]]; then
meson_configure+=$'\t'-Dupdate_server=starcatcher.us/TPT
if [[ $BSH_HOST_PLATFORM == emscripten ]]; then
meson_configure+=$'\t'-Dserver=tptserv.starcatcher.us
meson_configure+=$'\t'-Dstatic_server=tptserv.starcatcher.us/Static
fi
fi
if [[ $RELEASE_TYPE != dev ]]; then
meson_configure+=$'\t'-Dignore_updates=false

2
.github/prepare.py vendored
View File

@ -114,7 +114,7 @@ for arch, platform, libc, statdyn, bplatform, runso
( 'x86_64', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug', 0 ), # priority = 0: static debug build
( 'x86_64', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', True, True, '.pdb', None,'x86_64-win-msvc-static', 'release', 10 ),
( 'x86_64', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug', 10 ),
( 'x86_64', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, 'backendvs', None, 'debug', 0 ), # priority = 0: backend=vs build
# ( 'x86_64', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, 'backendvs', None, 'debug', 0 ), # priority = 0: backend=vs build
( 'x86_64', 'windows', 'msvc', 'dynamic', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'release', 10 ),
( 'x86', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', False, False, None, None, None, 'debug', 0 ), # priority = 0: static debug build
( 'x86', 'windows', 'msvc', 'static', 'windows', 'windows-2019', '.exe', True, True, '.pdb', None, 'i686-win-msvc-static', 'release', 10 ),

View File

@ -13,6 +13,8 @@ chmod 660 ~/.netrc
mountpoint=ftpmnt
mkdir $mountpoint
curlftpfs "$PUBLISH_HOSTPORT" $mountpoint -o ssl,ciphers='ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES128-GCM-SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_128_CCM_8_SHA256:TLS_AES_128_CCM_SHA256'
cp $PUBLISH_FILENAME $mountpoint/${PUBLISH_DIRECTORY:-.}/
if [[ -z ${PUBLISH_ACCESSCHECK-} ]]; then
cp $PUBLISH_FILENAME $mountpoint/${PUBLISH_DIRECTORY:-.}/
fi
fusermount -u $mountpoint
rmdir $mountpoint

View File

@ -41,6 +41,13 @@ jobs:
env:
PUBLISH_HOSTPORT: ${{ secrets.STARCATCHER_PUBLISH_HOSTPORT }}
GITHUB_REF: ${{ github.ref }}
- if: steps.prepare.outputs.do_publish == 'yes'
run: sudo apt update && sudo apt install curlftpfs && bash -c './.github/starcatcher-publish.sh'
env:
PUBLISH_HOSTPORT: ${{ secrets.STARCATCHER_PUBLISH_HOSTPORT }}
PUBLISH_USERNAME: ${{ secrets.STARCATCHER_PUBLISH_USERNAME }}
PUBLISH_PASSWORD: ${{ secrets.STARCATCHER_PUBLISH_PASSWORD }}
PUBLISH_ACCESSCHECK: yes
- if: steps.prepare.outputs.do_release == 'yes'
id: create_release
env:

View File

@ -1,4 +1,4 @@
The Powder Toy - January 2023
The Powder Toy - April 2024
==========================
Get the latest version [from the Powder Toy website](https://powdertoy.co.uk/Download.html).
@ -108,6 +108,7 @@ Controls
| Shift + R | Horizontal mirror for selected area when pasting stamps |
| Ctrl + Shift + R | Vertical mirror for selected area when pasting stamps |
| R | Rotate selected area counterclockwise when pasting stamps |
| F11 | Toggle fullscreen |
Command Line
---------------------------------------------------------------------------

View File

@ -376,6 +376,22 @@ if host_platform == 'emscripten'
'-o', app_exe + '.js', # so we get a .wasm, and a .js
]
endif
if get_option('export_lua_symbols')
if is_static and lua_variant != 'none' and not project_export_dynamic
if host_platform == 'windows'
error('Lua symbols are currently impossible to export correctly on Windows')
elif c_compiler.has_link_argument('-Wl,--export-dynamic-symbol')
project_link_args += [
'-Wl,--export-dynamic-symbol=lua_*',
'-Wl,--export-dynamic-symbol=luaL_*',
'-Wl,--export-dynamic-symbol=luaopen_*',
]
else
warning('your linker does not support -Wl,--export-dynamic-symbol so Meson will be instructed to export all symbols in order to enable loading Lua shared modules, which may blow up the size of the resulting binary')
project_export_dynamic = true
endif
endif
endif
if get_option('build_powder')
powder_deps += project_deps + [

View File

@ -40,42 +40,42 @@ option(
'display_version_major',
type: 'integer',
min: 0,
value: 97,
value: 98,
description: 'Major component of the display version, should more or less map to the MINOR version in semantic versioning'
)
option(
'display_version_minor',
type: 'integer',
min: 0,
value: 0,
value: 2,
description: 'Minor component of the display version, should more or less map to the PATCH version in semantic versioning'
)
option(
'build_num',
type: 'integer',
min: 0,
value: 360,
value: 365,
description: 'Build number, should be strictly monotonously increasing across public releases'
)
option(
'upstream_version_major',
type: 'integer',
min: 0,
value: 97,
value: 98,
description: 'Major component of the upstream display version, mod owners should not change this but merge upstream changes to it'
)
option(
'upstream_version_minor',
type: 'integer',
min: 0,
value: 0,
value: 2,
description: 'Minor component of the upstream display version, mod owners should not change this but merge upstream changes to it'
)
option(
'upstream_build_num',
type: 'integer',
min: 0,
value: 360,
value: 365,
description: 'Upstream build number, mod owners should not change this but merge upstream changes to it'
)
option(
@ -305,3 +305,9 @@ option(
value: true,
description: 'Ask Windows nicely for UTF-8 as the codepage'
)
option(
'export_lua_symbols',
type: 'boolean',
value: false,
description: 'Export Lua symbols to enable loading of Lua shared modules'
)

View File

@ -445,19 +445,20 @@ int Main(int argc, char *argv[])
engine.Begin();
engine.SetFastQuit(prefs.Get("FastQuit", true));
engine.TouchUI = prefs.Get("TouchUI", DEFAULT_TOUCH_UI);
engine.windowFrameOps = windowFrameOps;
SDLOpen();
if (Client::Ref().IsFirstRun() && FORCE_WINDOW_FRAME_OPS == forceWindowFrameOpsNone)
{
auto guessed = GuessBestScale();
if (windowFrameOps.scale != guessed)
if (engine.windowFrameOps.scale != guessed)
{
windowFrameOps.scale = guessed;
engine.windowFrameOps.scale = guessed;
prefs.Set("Scale", windowFrameOps.scale);
showLargeScreenDialog = true;
}
}
engine.windowFrameOps = windowFrameOps;
SDLOpen();
bool enableBluescreen = USE_BLUESCREEN && !true_arg(arguments["disable-bluescreen"]);
if (enableBluescreen)

View File

@ -22,6 +22,8 @@ int main(int argc, char *argv[])
auto inputFilename = ByteString(argv[1]);
auto outputFilename = ByteString(argv[2]) + ".png";
auto simulationData = std::make_unique<SimulationData>();
std::vector<char> fileData;
if (!Platform::ReadFile(fileData, inputFilename))
{
@ -40,7 +42,6 @@ int main(int argc, char *argv[])
throw e;
}
auto simulationData = std::make_unique<SimulationData>();
Simulation * sim = new Simulation();
Renderer * ren = new Renderer(sim);
@ -63,6 +64,7 @@ int main(int argc, char *argv[])
}
else
{
ren->clearScreen();
int w = Graphics::TextSize("Save file invalid").X + 15, x = (XRES-w)/2, y = (YRES-24)/2;
ren->DrawRect(RectSized(Vec2{ x, y }, Vec2{ w, 24 }), 0xC0C0C0_rgb);
ren->BlendText({ x+8, y+8 }, "Save file invalid", 0xC0C0F0_rgb .WithAlpha(255));

View File

@ -3,7 +3,6 @@
#include "client/http/StartupRequest.h"
#include "ClientListener.h"
#include "Format.h"
#include "MD5.h"
#include "client/GameSave.h"
#include "client/SaveFile.h"
#include "client/SaveInfo.h"
@ -118,7 +117,7 @@ void Client::Tick()
{
if (versionCheckRequest->StatusCode() == 618)
{
AddServerNotification({ "Failed to load SSL certificates", ByteString(SCHEME) + "powdertoy.co.uk/FAQ.html" });
AddServerNotification({ "Failed to load SSL certificates", ByteString::Build(SCHEME, SERVER, "/FAQ.html") });
}
try
{
@ -378,7 +377,6 @@ void Client::RescanStamps()
newStampIDs.push_back(stampID);
}
}
auto oldCount = newStampIDs.size();
auto stampIDsSet = std::set<ByteString>(stampIDs.begin(), stampIDs.end());
for (auto &stampID : stampFilesSet)
{
@ -390,8 +388,6 @@ void Client::RescanStamps()
}
if (changed)
{
// Move newly discovered stamps to front.
std::rotate(newStampIDs.begin(), newStampIDs.begin() + oldCount, newStampIDs.end());
stampIDs = newStampIDs;
WriteStamps();
}

View File

@ -51,9 +51,18 @@ GameSave::GameSave(const std::vector<char> &data, bool newWantAuthors)
void GameSave::MapPalette()
{
int partMap[PT_NUM];
bool ignoreMissingErrors[PT_NUM];
for(int i = 0; i < PT_NUM; i++)
{
partMap[i] = i;
ignoreMissingErrors[i] = false;
}
if (version <= Version(98, 2))
{
ignoreMissingErrors[PT_ICEI] = true;
ignoreMissingErrors[PT_SNOW] = true;
ignoreMissingErrors[PT_RSST] = true;
ignoreMissingErrors[PT_RSSS] = true;
}
auto &sd = SimulationData::CRef();
@ -90,12 +99,14 @@ void GameSave::MapPalette()
}
}
}
auto paletteLookup = [this, &partMap](int type) {
auto paletteLookup = [this, &partMap](int type, bool ignoreMissingErrors) {
if (type > 0 && type < PT_NUM)
{
auto carriedType = partMap[type];
if (!carriedType) // type is not 0 so this shouldn't be 0 either
{
if (ignoreMissingErrors)
return type;
missingElements.ids.insert(type);
}
type = carriedType;
@ -113,7 +124,7 @@ void GameSave::MapPalette()
{
continue;
}
tempPart.type = paletteLookup(tempPart.type);
tempPart.type = paletteLookup(tempPart.type, false);
for (auto index : possiblyCarriesType)
{
if (elements[tempPart.type].CarriesTypeIn & (1U << index))
@ -121,7 +132,7 @@ void GameSave::MapPalette()
auto *prop = reinterpret_cast<int *>(reinterpret_cast<char *>(&tempPart) + properties[index].Offset);
auto carriedType = *prop & int(pmapmask);
auto extra = *prop >> pmapbits;
carriedType = paletteLookup(carriedType);
carriedType = paletteLookup(carriedType, ignoreMissingErrors[tempPart.type]);
*prop = PMAP(extra, carriedType);
}
}
@ -2336,6 +2347,14 @@ std::pair<bool, std::vector<char>> GameSave::serialiseOPS() const
{
RESTRICTVERSION(97, 0);
}
if (particles[i].type == PT_RSST || particles[i].type == PT_RSSS)
{
RESTRICTVERSION(98, 0);
}
if (particles[i].type == PT_ETRD && (particles[i].tmp || particles[i].tmp2))
{
RESTRICTVERSION(98, 0);
}
//Get the pmap entry for the next particle in the same position
i = partsPosLink[i];

View File

@ -1,231 +0,0 @@
// based on public-domain code from Colin Plumb (1993)
#include "MD5.h"
#include <cstring>
static unsigned getu32(const unsigned char *addr)
{
return (((((unsigned long)addr[3] << 8) | addr[2]) << 8) | addr[1]) << 8 | addr[0];
}
static void putu32(unsigned data, unsigned char *addr)
{
addr[0] = (unsigned char)data;
addr[1] = (unsigned char)(data >> 8);
addr[2] = (unsigned char)(data >> 16);
addr[3] = (unsigned char)(data >> 24);
}
void md5_init(struct md5_context *ctx)
{
ctx->buf[0] = 0x67452301;
ctx->buf[1] = 0xefcdab89;
ctx->buf[2] = 0x98badcfe;
ctx->buf[3] = 0x10325476;
ctx->bits[0] = 0;
ctx->bits[1] = 0;
}
void md5_update(struct md5_context *ctx, unsigned char const *buf, unsigned len)
{
unsigned t;
// update bit count
t = ctx->bits[0];
if ((ctx->bits[0] = (t + ((unsigned)len << 3)) & 0xffffffff) < t)
ctx->bits[1]++; // carry
ctx->bits[1] += len >> 29;
t = (t >> 3) & 0x3f;
// use leading data to top up the buffer
if (t)
{
unsigned char *p = ctx->in + t;
t = 64-t;
if (len < t)
{
memcpy(p, buf, len);
return;
}
memcpy(p, buf, t);
md5_transform(ctx->buf, ctx->in);
buf += t;
len -= t;
}
// following 64-byte chunks
while (len >= 64)
{
memcpy(ctx->in, buf, 64);
md5_transform(ctx->buf, ctx->in);
buf += 64;
len -= 64;
}
// save rest of bytes for later
memcpy(ctx->in, buf, len);
}
void md5_final(unsigned char digest[16], struct md5_context *ctx)
{
unsigned count;
unsigned char *p;
// #bytes mod64
count = (ctx->bits[0] >> 3) & 0x3F;
// first char of padding = 0x80
p = ctx->in + count;
*p++ = 0x80;
// calculate # of bytes to pad
count = 64 - 1 - count;
// Pad out to 56 mod 64
if (count < 8)
{
// we need to finish a whole block before padding
memset(p, 0, count);
md5_transform(ctx->buf, ctx->in);
memset(ctx->in, 0, 56);
}
else
{
// just pad to 56 bytes
memset(p, 0, count-8);
}
// append length & final transform
putu32(ctx->bits[0], ctx->in + 56);
putu32(ctx->bits[1], ctx->in + 60);
md5_transform(ctx->buf, ctx->in);
putu32(ctx->buf[0], digest);
putu32(ctx->buf[1], digest + 4);
putu32(ctx->buf[2], digest + 8);
putu32(ctx->buf[3], digest + 12);
memset(&ctx, 0, sizeof(ctx));
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w &= 0xffffffff, w = w<<s | w>>(32-s), w += x )
void md5_transform(unsigned buf[4], const unsigned char inraw[64])
{
unsigned a, b, c, d;
unsigned in[16];
int i;
for (i = 0; i < 16; ++i)
in[i] = getu32 (inraw + 4 * i);
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
}
static char hexChars[] = "0123456789abcdef";
void md5_ascii(char *result, unsigned char const *buf, unsigned len)
{
struct md5_context md5;
unsigned char hash[16];
int i;
if (len==0)
len = strlen((char *)buf);
md5_init(&md5);
md5_update(&md5, buf, len);
md5_final(hash, &md5);
for (i=0; i<16; i++)
{
result[i*2] = hexChars[(hash[i]>>4)&0xF];
result[i*2+1] = hexChars[hash[i]&0x0F];
}
result[32] = 0;
}

View File

@ -1,15 +0,0 @@
#pragma once
struct md5_context
{
unsigned buf[4];
unsigned bits[2];
unsigned char in[64];
};
void md5_init(struct md5_context *context);
void md5_update(struct md5_context *context, unsigned char const *buf, unsigned len);
void md5_final(unsigned char digest[16], struct md5_context *context);
void md5_transform(unsigned buf[4], const unsigned char in[64]);
void md5_ascii(char *result, unsigned char const *buf, unsigned len);

View File

@ -14,4 +14,13 @@ namespace http
sortByVotes,
sortByDate,
};
enum Period
{
allSaves,
todaySaves,
weekSaves,
monthSaves,
yearSaves,
};
}

View File

@ -1,3 +1,4 @@
#include <ctime>
#include "SearchSavesRequest.h"
#include "Config.h"
#include "client/Client.h"
@ -6,7 +7,7 @@
namespace http
{
static ByteString Url(int start, int count, ByteString query, Sort sort, Category category)
static ByteString Url(int start, int count, ByteString query, Period period, Sort sort, Category category)
{
ByteStringBuilder builder;
builder << SCHEME << SERVER << "/Browse.json?Start=" << start << "&Count=" << count;
@ -17,6 +18,38 @@ namespace http
}
query += str;
};
time_t currentTime = time(NULL);
if(period)
{
switch (period)
{
case todaySaves:
currentTime -= 60*60*24; // One day
break;
case weekSaves:
currentTime -= 60*60*24*7; // One week
break;
case monthSaves:
currentTime -= 60*60*24*31; // One month
break;
case yearSaves:
currentTime -= 60*60*24*365; // One year
break;
default:
break;
}
struct tm currentTimeData = *localtime(&currentTime);
ByteStringBuilder afterQuery;
afterQuery << "after:" << currentTimeData.tm_year+1900 << "-" <<
(currentTimeData.tm_mon < 9 ? "0" : "") << currentTimeData.tm_mon+1 << "-" <<
(currentTimeData.tm_mday < 10 ? "0" : "") << currentTimeData.tm_mday;
appendToQuery(afterQuery.Build());
}
switch (sort)
{
case sortByDate:
@ -48,7 +81,7 @@ namespace http
return builder.Build();
}
SearchSavesRequest::SearchSavesRequest(int start, int count, ByteString query, Sort sort, Category category) : APIRequest(Url(start, count, query, sort, category), authUse, false)
SearchSavesRequest::SearchSavesRequest(int start, int count, ByteString query, Period period, Sort sort, Category category) : APIRequest(Url(start, count, query, period, sort, category), authUse, false)
{
}

View File

@ -8,7 +8,7 @@ namespace http
class SearchSavesRequest : public APIRequest
{
public:
SearchSavesRequest(int start, int count, ByteString query, Sort sort, Category category);
SearchSavesRequest(int start, int count, ByteString query, Period period, Sort sort, Category category);
std::pair<int, std::vector<std::unique_ptr<SaveInfo>>> Finish();
};

View File

@ -52,6 +52,10 @@ namespace http
return;
}
auto &info = versions[key];
if (info.isNull())
{
return;
}
auto getOr = [&info](ByteString key, int defaultValue) -> int {
if (!info.isMember(key))
{
@ -59,7 +63,7 @@ namespace http
}
return info[key].asInt();
};
auto build = getOr(key == "Snapshot" ? "Snapshot" : "Build", -1);
auto build = getOr(key == "Snapshot" ? "Snapshot" : "Build", 0);
if (!updateAvailableFunc(build))
{
return;
@ -68,8 +72,8 @@ namespace http
channel,
ByteString::Build(SCHEME, alternate ? UPDATESERVER : SERVER, info["File"].asString()),
ByteString(info["Changelog"].asString()).FromUtf8(),
getOr("Major", -1),
getOr("Minor", -1),
getOr("Major", 0),
getOr("Minor", 0),
build,
};
};

View File

@ -1,5 +1,4 @@
client_files = files(
'MD5.cpp',
'SaveFile.cpp',
'SaveInfo.cpp',
'ThumbnailRendererTask.cpp',

View File

@ -112,6 +112,14 @@ struct Vec2
);
}
Vec2<T> Min(Vec2<T> other) const
{
return Vec2<T>(
std::min(X, other.X),
std::min(Y, other.Y)
);
}
// Return a rectangle starting at origin, whose dimensions match this vector
template<typename S = T, typename = std::enable_if_t<std::is_integral_v<S>>>
constexpr inline Rect<T> OriginRect() const

View File

@ -58,7 +58,7 @@ bool ReadFile(std::vector<char> &fileData, ByteString filename)
if (f) f.seekg(0, std::ios::end);
if (f) fileData.resize(f.tellg());
if (f) f.seekg(0);
if (f) f.read(&fileData[0], fileData.size());
if (f && fileData.size()) f.read(&fileData[0], fileData.size());
if (!f)
{
std::cerr << "ReadFile: " << filename << ": " << strerror(errno) << std::endl;

View File

@ -183,6 +183,8 @@ void Renderer::render_parts()
gfctx.pipeSubcallTpart = nullptr;
int deca, decr, decg, decb, cola, colr, colg, colb, firea, firer, fireg, fireb, pixel_mode, q, i, t, nx, ny, x, y;
int orbd[4] = {0, 0, 0, 0}, orbl[4] = {0, 0, 0, 0};
int drawing_budget = 1000000; //Serves as an upper bound for costly effects such as SPARK, FLARE and LFLARE
if(!sim)
return;
auto *parts = sim->parts;
@ -633,7 +635,7 @@ void Renderer::render_parts()
{
auto flicker = float(gfctx.rng()%20);
auto gradv = 4*sim->parts[i].life + flicker;
for (x = 0; gradv>0.5; x++) {
for (x = 0; (gradv>0.5) && (drawing_budget > 0); x++) {
auto col = RGBA<uint8_t>(
std::min(0xFF, colr * int(gradv) / 255),
std::min(0xFF, colg * int(gradv) / 255),
@ -644,6 +646,7 @@ void Renderer::render_parts()
AddPixel({ nx, ny+x }, col);
AddPixel({ nx, ny-x }, col);
gradv = gradv/1.5f;
drawing_budget--;
}
}
if(pixel_mode & PMODE_FLARE)
@ -660,12 +663,13 @@ void Renderer::render_parts()
BlendPixel({ nx-1, ny-1 }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
BlendPixel({ nx+1, ny+1 }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
BlendPixel({ nx-1, ny+1 }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
for (x = 1; gradv>0.5; x++) {
for (x = 1; (gradv>0.5) && (drawing_budget > 0); x++) {
AddPixel({ nx+x, ny }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
AddPixel({ nx-x, ny }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
AddPixel({ nx, ny+x }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
AddPixel({ nx, ny-x }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
gradv = gradv/1.2f;
drawing_budget--;
}
}
if(pixel_mode & PMODE_LFLARE)
@ -682,12 +686,13 @@ void Renderer::render_parts()
BlendPixel({ nx-1, ny-1 }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
BlendPixel({ nx+1, ny+1 }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
BlendPixel({ nx-1, ny+1 }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
for (x = 1; gradv>0.5; x++) {
for (x = 1; (gradv>0.5) && (drawing_budget > 0); x++) {
AddPixel({ nx+x, ny }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
AddPixel({ nx-x, ny }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
AddPixel({ nx, ny+x }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
AddPixel({ nx, ny-x }, RGBA<uint8_t>(colr, colg, colb, int(gradv)));
gradv = gradv/1.01f;
drawing_budget--;
}
}
if (pixel_mode & EFFECT_GRAVIN)

View File

@ -192,7 +192,11 @@ void ElementSearchActivity::searchTools(String query)
}
}
scrollPanel->InnerSize = ui::Point(scrollPanel->Size.X, current.Y + 1);
if (current.X == 0)
{
current.Y -= 19;
}
scrollPanel->InnerSize = ui::Point(scrollPanel->Size.X, current.Y + 20);
}
void ElementSearchActivity::SetActiveTool(int selectionState, Tool * tool)

View File

@ -39,7 +39,7 @@ class LoadFilesTask: public Task
bool doWork() override
{
std::vector<ByteString> files = Platform::DirectorySearch(directory, search, { ".cps" });
std::sort(files.rbegin(), files.rend(), [](ByteString a, ByteString b) { return a.ToLower() < b.ToLower(); });
std::sort(files.rbegin(), files.rend(), [](ByteString a, ByteString b) { return a.ToLower() > b.ToLower(); });
notifyProgress(-1);
for(std::vector<ByteString>::iterator iter = files.begin(), end = files.end(); iter != end; ++iter)

View File

@ -552,7 +552,7 @@ bool GameController::MouseUp(int x, int y, unsigned button, MouseupReason reason
}
break;
case sign::Type::Thread:
Platform::OpenURI(ByteString::Build(SCHEME, "powdertoy.co.uk/Discussions/Thread/View.html?Thread=", str.Substr(3, si.first - 3).ToUtf8()));
Platform::OpenURI(ByteString::Build(SCHEME, SERVER, "/Discussions/Thread/View.html?Thread=", str.Substr(3, si.first - 3).ToUtf8()));
break;
case sign::Type::Search:
OpenSearch(str.Substr(3, si.first - 3));
@ -1111,8 +1111,11 @@ void GameController::SetActiveTool(int toolSelection, Tool * tool)
gameModel->SetLastTool(tool);
for(int i = 0; i < 3; i++)
{
if(gameModel->GetActiveTool(i) == gameModel->GetMenuList().at(SC_WALL)->GetToolList().at(WL_GRAV))
auto *activeTool = gameModel->GetActiveTool(i);
if (activeTool && activeTool->Identifier == "DEFAULT_WL_GRVTY")
{
gameModel->GetRenderer()->gravityZonesEnabled = true;
}
}
if (tool->Identifier == "DEFAULT_UI_PROPERTY")
{

View File

@ -76,7 +76,7 @@ inline ByteString IntroText()
}
else
{
sb << "\bgTo use online features such as saving, you need to register at: \brhttps://powdertoy.co.uk/Register.html\n";
sb << "\bgTo use online features such as saving, you need to register at: \brhttps://" << SERVER << "/Register.html\n";
}
sb << "\n\bt" << VersionInfo();
return sb.Build();

View File

@ -15,7 +15,8 @@ class DropDownWindow : public ui::Window
public:
DropDownWindow(DropDown * dropDown):
Window(dropDown->GetScreenPos() + ui::Point(-1, -1 - dropDown->optionIndex * 16), ui::Point(dropDown->Size.X+2, 2+dropDown->options.size()*16)),
Window(dropDown->GetScreenPos() + ui::Point(-1, -1 - (dropDown->optionIndex*16 < dropDown->GetScreenPos().Y ? dropDown->optionIndex*16 : 0)),
ui::Point(dropDown->Size.X+2, 2+dropDown->options.size()*16)),
dropDown(dropDown),
appearance(dropDown->Appearance)
{

View File

@ -83,6 +83,8 @@ void Engine::ShowWindow(Window * window)
{
window->Position.Y = (g->Size().Y - window->Size.Y) / 2;
}
window->Size = window->Size.Min(g->Size());
window->Position = window->Position.Clamp(RectBetween<int>({0, 0}, g->Size()));
/*if(window->Position.Y > 0)
{
windowTargetPosition = window->Position;

View File

@ -114,32 +114,35 @@ void Panel::OnMouseClick(int localx, int localy, unsigned button)
void Panel::OnMouseDown(int x, int y, unsigned button)
{
auto localx = x - Position.X;
auto localy = y - Position.Y;
//check if clicked a child
for(int i = children.size()-1; i >= 0 ; --i)
if (MouseDownInside)
{
//child must be enabled
if(children[i]->Enabled)
auto localx = x - Position.X;
auto localy = y - Position.Y;
//check if clicked a child
for(int i = children.size()-1; i >= 0 ; --i)
{
//is mouse inside?
if( localx >= children[i]->Position.X + ViewportPosition.X &&
localy >= children[i]->Position.Y + ViewportPosition.Y &&
localx < children[i]->Position.X + ViewportPosition.X + children[i]->Size.X &&
localy < children[i]->Position.Y + ViewportPosition.Y + children[i]->Size.Y )
//child must be enabled
if(children[i]->Enabled)
{
GetParentWindow()->FocusComponent(children[i]);
children[i]->MouseDownInside = true;
break;
//is mouse inside?
if( localx >= children[i]->Position.X + ViewportPosition.X &&
localy >= children[i]->Position.Y + ViewportPosition.Y &&
localx < children[i]->Position.X + ViewportPosition.X + children[i]->Size.X &&
localy < children[i]->Position.Y + ViewportPosition.Y + children[i]->Size.Y )
{
GetParentWindow()->FocusComponent(children[i]);
children[i]->MouseDownInside = true;
break;
}
}
}
}
XOnMouseDown(x, y, button);
for (size_t i = 0; i < children.size(); ++i)
{
if(children[i]->Enabled)
children[i]->OnMouseDown(x - Position.X - ViewportPosition.X, y - Position.Y - ViewportPosition.Y, button);
XOnMouseDown(x, y, button);
for (size_t i = 0; i < children.size(); ++i)
{
if(children[i]->Enabled)
children[i]->OnMouseDown(x - Position.X - ViewportPosition.X, y - Position.Y - ViewportPosition.Y, button);
}
}
}
@ -150,12 +153,14 @@ void Panel::OnMouseHover(int localx, int localy)
{
if (children[i]->Enabled)
{
if( localx >= children[i]->Position.X &&
localy >= children[i]->Position.Y &&
localx < children[i]->Position.X + children[i]->Size.X &&
localy < children[i]->Position.Y + children[i]->Size.Y )
auto px = children[i]->Position.X + ViewportPosition.X;
auto py = children[i]->Position.Y + ViewportPosition.Y;
if( localx >= px &&
localy >= py &&
localx < px + children[i]->Size.X &&
localy < py + children[i]->Size.Y )
{
children[i]->OnMouseHover(localx - children[i]->Position.X, localy - children[i]->Position.Y);
children[i]->OnMouseHover(localx - px, localy - py);
break;
}
}

View File

@ -361,10 +361,10 @@ void SaveButton::OnMouseMoved(int x, int y)
isMouseInsideHistory = false;
if (MouseInside)
{
if(y > Size.Y-11)
if (y > Size.Y-11)
isMouseInsideAuthor = true;
if(showVotes && y > Size.Y-29 && y < Size.Y - 18 && x > 0 && x < 9)
if (y > Size.Y-29 && y < Size.Y - 18 && x > 0 && x < 9)
isMouseInsideHistory = true;
}
}

View File

@ -75,6 +75,7 @@ void ScrollPanel::XOnMouseDown(int x, int y, unsigned int button)
{
if (MouseDownInside)
{
CancelPanning();
if (isMouseInsideScrollbar)
{
scrollbarSelected = true;
@ -86,24 +87,31 @@ void ScrollPanel::XOnMouseDown(int x, int y, unsigned int button)
}
}
void ScrollPanel::CancelPanning()
{
panning = false;
panHistory = {};
yScrollVel = 0;
}
void ScrollPanel::XOnMouseUp(int x, int y, unsigned int button)
{
scrollbarSelected = false;
panning = false;
auto oldPanHistory = panHistory;
CancelPanning();
{
auto it = panHistory.end();
while (it != panHistory.begin() && *(it - 1))
auto it = oldPanHistory.end();
while (it != oldPanHistory.begin() && *(it - 1))
{
--it;
}
if (it < panHistory.end())
if (it < oldPanHistory.end())
{
auto offsetYDiff = panHistory.back()->offsetY - (*it)->offsetY;
auto tickDiff = panHistory.back()->ticks - (*it)->ticks;
auto offsetYDiff = oldPanHistory.back()->offsetY - (*it)->offsetY;
auto tickDiff = oldPanHistory.back()->ticks - (*it)->ticks;
yScrollVel += offsetYDiff / tickDiff * (1000.f / Engine::Ref().GetFps());
}
}
panHistory = {};
isMouseInsideScrollbarArea = false;
scrollbarClickLocation = 0;
}

View File

@ -7,6 +7,8 @@ namespace ui
{
class ScrollPanel: public Panel
{
void CancelPanning();
protected:
int scrollBarWidth;
Point maxOffset;

View File

@ -50,8 +50,11 @@ void Slider::OnMouseMoved(int x, int y)
void Slider::OnMouseDown(int x, int y, unsigned button)
{
isMouseDown = true;
updatePosition(x - Position.X);
if (MouseDownInside)
{
isMouseDown = true;
updatePosition(x - Position.X);
}
}
void Slider::OnMouseUp(int x, int y, unsigned button)

View File

@ -59,6 +59,8 @@ void LocalBrowserModel::OpenSave(int index)
{
stamp = std::move(savesList[index]);
savesList.clear();
notifyPageChanged();
notifySavesListChanged();
}
bool LocalBrowserModel::GetMoveToFront()

View File

@ -1,5 +1,6 @@
#include "LoginModel.h"
#include "LoginView.h"
#include "Config.h"
#include "client/Client.h"
#include "client/http/LoginRequest.h"
#include "client/http/LogoutRequest.h"
@ -8,7 +9,7 @@ void LoginModel::Login(ByteString username, ByteString password)
{
if (username.Contains("@"))
{
statusText = "Use your Powder Toy account to log in, not your email. If you don't have a Powder Toy account, you can create one at https://powdertoy.co.uk/Register.html";
statusText = String::Build("Use your Powder Toy account to log in, not your email. If you don't have a Powder Toy account, you can create one at https://", SERVER, "/Register.html");
loginStatus = loginIdle;
notifyStatusChanged();
return;

View File

@ -76,13 +76,13 @@ OptionsView::OptionsView() : ui::Window(ui::Point(-1, -1), ui::Point(320, 340))
auto *checkbox = new ui::Checkbox(ui::Point(8 + indent * 15, currentY), ui::Point(1, 16), text, "");
autoWidth(checkbox, 0);
checkbox->SetActionCallback({ action });
scrollPanel->AddChild(checkbox);
currentY += 14;
if (info.size())
{
addLabel(indent, info);
}
currentY += 4;
scrollPanel->AddChild(checkbox);
return checkbox;
};
auto addDropDown = [this, &currentY, &autoWidth](String info, std::vector<std::pair<String, int>> options, std::function<void ()> action) {
@ -137,10 +137,11 @@ OptionsView::OptionsView() : ui::Window(ui::Point(-1, -1), ui::Point(320, 340))
ambientAirTemp->SetDefocusCallback({ [this] {
UpdateAirTemp(ambientAirTemp->GetText(), true);
}});
ambientAirTemp->SetLimit(9);
scrollPanel->AddChild(ambientAirTemp);
ambientAirTempPreview = new ui::Button(ui::Point(Size.X-31, currentY), ui::Point(16, 16), "", "Preview");
scrollPanel->AddChild(ambientAirTempPreview);
auto *label = new ui::Label(ui::Point(8, currentY), ui::Point(Size.X-96, 16), "Ambient air temperature");
auto *label = new ui::Label(ui::Point(8, currentY), ui::Point(Size.X-105, 16), "Ambient air temperature");
label->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
label->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
scrollPanel->AddChild(label);

View File

@ -147,7 +147,7 @@ void PreviewModel::OnSaveReady()
{
auto gameSave = std::make_unique<GameSave>(*saveData);
if (gameSave->fromNewerVersion)
new ErrorMessage("This save is from a newer version", "Please update TPT in game or at https://powdertoy.co.uk");
new ErrorMessage("This save is from a newer version", String::Build("Please update TPT in game or at https://", SERVER));
saveInfo->SetGameSave(std::move(gameSave));
}
catch(ParseException &e)

View File

@ -111,9 +111,9 @@ PreviewView::PreviewView(std::unique_ptr<VideoBuffer> newSavePreview):
AddComponent(missingElementsButton);
if(showAvatars)
saveNameLabel = new ui::Label(ui::Point(39, (YRES/2)+4), ui::Point(180, 16), "");
saveNameLabel = new ui::Label(ui::Point(39, (YRES/2)+4), ui::Point(265, 16), "");
else
saveNameLabel = new ui::Label(ui::Point(5, (YRES/2)+4), ui::Point(200, 16), "");
saveNameLabel = new ui::Label(ui::Point(5, (YRES/2)+4), ui::Point(300, 16), "");
saveNameLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
saveNameLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
AddComponent(saveNameLabel);
@ -129,9 +129,9 @@ PreviewView::PreviewView(std::unique_ptr<VideoBuffer> newSavePreview):
AddComponent(saveDescriptionLabel);
if(showAvatars)
authorDateLabel = new ui::Label(ui::Point(39, (YRES/2)+4+15), ui::Point(180, 16), "");
authorDateLabel = new ui::Label(ui::Point(39, (YRES/2)+4+15), ui::Point(200, 16), "");
else
authorDateLabel = new ui::Label(ui::Point(5, (YRES/2)+4+15), ui::Point(200, 16), "");
authorDateLabel = new ui::Label(ui::Point(5, (YRES/2)+4+15), ui::Point(220, 16), "");
authorDateLabel->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
authorDateLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
AddComponent(authorDateLabel);
@ -148,7 +148,7 @@ PreviewView::PreviewView(std::unique_ptr<VideoBuffer> newSavePreview):
AddComponent(avatarButton);
}
viewsLabel = new ui::Label(ui::Point((XRES/2)-80, (YRES/2)+4+15), ui::Point(80, 16), "");
viewsLabel = new ui::Label(ui::Point((XRES/2)-88, (YRES/2)+4+15), ui::Point(88, 16), "");
viewsLabel->Appearance.HorizontalAlign = ui::Appearance::AlignRight;
viewsLabel->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
AddComponent(viewsLabel);
@ -644,6 +644,7 @@ void PreviewView::NotifyCommentBoxEnabledChanged(PreviewModel * sender)
} });
addCommentBox->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
addCommentBox->SetMultiline(true);
addCommentBox->SetLimit(1000);
AddComponent(addCommentBox);
submitCommentButton = new ui::Button(ui::Point(Size.X-40, Size.Y-19), ui::Point(40, 19), "Submit");
submitCommentButton->SetActionCallback({ [this] { submitComment(); } });

View File

@ -70,7 +70,7 @@ void ProfileActivity::setUserInfo(UserInfo newInfo)
int currentY = 5;
// username label
ui::Label * title = new ui::Label(ui::Point(4, currentY), ui::Point(Size.X-8-(40+8+75), 15), info.username.FromUtf8());
ui::Label * title = new ui::Label(ui::Point(4, currentY), ui::Point(Size.X-8-(40+16+75), 15), info.username.FromUtf8());
title->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
scrollPanel->AddChild(title);
@ -90,13 +90,13 @@ void ProfileActivity::setUserInfo(UserInfo newInfo)
currentY += 23;
// age
ui::Label * ageTitle = new ui::Label(ui::Point(4, currentY), ui::Point(18, 15), "Age:");
ui::Label * ageTitle = new ui::Label(ui::Point(4, currentY), ui::Point(23, 15), "Age:");
ageTitle->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
ageTitle->SetTextColour(ui::Colour(180, 180, 180));
scrollPanel->AddChild(ageTitle);
// can't figure out how to tell a null from a 0 in the json library we use
ui::Label *age = new ui::Label(ui::Point(8+ageTitle->Size.X, currentY), ui::Point(40, 15), info.age ? String::Build(info.age) : "\bgNot Provided");
ui::Label *age = new ui::Label(ui::Point(5+ageTitle->Size.X, currentY), ui::Point(Size.X-ageTitle->Size.X-56, 15), info.age ? String::Build(info.age) : "\bgNot Provided");
age->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
scrollPanel->AddChild(age);
currentY += 2+age->Size.Y;
@ -108,20 +108,25 @@ void ProfileActivity::setUserInfo(UserInfo newInfo)
scrollPanel->AddChild(locationTitle);
if (editable)
location = new ui::Textbox(ui::Point(8+locationTitle->Size.X, currentY), ui::Point(Size.X-locationTitle->Size.X-16, 17), info.location);
{
location = new ui::Textbox(ui::Point(5+locationTitle->Size.X, currentY), ui::Point(Size.X-locationTitle->Size.X-16, 17), info.location);
((ui::Textbox*)location)->SetLimit(40);
}
else
location = new ui::Label(ui::Point(4+locationTitle->Size.X, currentY), ui::Point(Size.X-locationTitle->Size.X-14, 17), info.location);
{
location = new ui::Label(ui::Point(5+locationTitle->Size.X, currentY), ui::Point(Size.X-locationTitle->Size.X-14, 17), info.location);
}
location->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
scrollPanel->AddChild(location);
currentY += 2+location->Size.Y;
// website
ui::Label * websiteTitle = new ui::Label(ui::Point(4, currentY), ui::Point(38, 15), "Website:");
ui::Label * websiteTitle = new ui::Label(ui::Point(1, currentY), ui::Point(42, 15), "Website:");
websiteTitle->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
websiteTitle->SetTextColour(ui::Colour(180, 180, 180));
scrollPanel->AddChild(websiteTitle);
ui::Label *website = new ui::Label(ui::Point(8+websiteTitle->Size.X, currentY), ui::Point(Size.X-websiteTitle->Size.X-16, 15), info.website.FromUtf8());
ui::Label *website = new ui::Label(ui::Point(2+websiteTitle->Size.X, currentY), ui::Point(Size.X-websiteTitle->Size.X-16, 15), info.website.FromUtf8());
website->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
scrollPanel->AddChild(website);
currentY += 2+website->Size.Y;
@ -134,34 +139,34 @@ void ProfileActivity::setUserInfo(UserInfo newInfo)
currentY += savesTitle->Size.Y;
// saves count
ui::Label * saveCountTitle = new ui::Label(ui::Point(12, currentY), ui::Point(30, 15), "Count:");
ui::Label * saveCountTitle = new ui::Label(ui::Point(12, currentY), ui::Point(32, 15), "Count:");
saveCountTitle->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
saveCountTitle->SetTextColour(ui::Colour(180, 180, 180));
scrollPanel->AddChild(saveCountTitle);
ui::Label *savesCount = new ui::Label(ui::Point(12+saveCountTitle->Size.X, currentY), ui::Point(Size.X-saveCountTitle->Size.X-16, 15), String::Build(info.saveCount));
ui::Label *savesCount = new ui::Label(ui::Point(13+saveCountTitle->Size.X, currentY), ui::Point(Size.X-saveCountTitle->Size.X-24, 15), String::Build(info.saveCount));
savesCount->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
scrollPanel->AddChild(savesCount);
currentY += savesCount->Size.Y;
// average score
ui::Label * averageScoreTitle = new ui::Label(ui::Point(12, currentY), ui::Point(70, 15), "Average Score:");
ui::Label * averageScoreTitle = new ui::Label(ui::Point(12, currentY), ui::Point(72, 15), "Average Score:");
averageScoreTitle->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
averageScoreTitle->SetTextColour(ui::Colour(180, 180, 180));
scrollPanel->AddChild(averageScoreTitle);
ui::Label *averageScore = new ui::Label(ui::Point(12+averageScoreTitle->Size.X, currentY), ui::Point(Size.X-averageScoreTitle->Size.X-16, 15), String::Build(info.averageScore));
ui::Label *averageScore = new ui::Label(ui::Point(13+averageScoreTitle->Size.X, currentY), ui::Point(Size.X-averageScoreTitle->Size.X-24, 15), String::Build(info.averageScore));
averageScore->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
scrollPanel->AddChild(averageScore);
currentY += averageScore->Size.Y;
// highest score
ui::Label * highestScoreTitle = new ui::Label(ui::Point(12, currentY), ui::Point(69, 15), "Highest Score:");
ui::Label * highestScoreTitle = new ui::Label(ui::Point(12, currentY), ui::Point(71, 15), "Highest Score:");
highestScoreTitle->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
highestScoreTitle->SetTextColour(ui::Colour(180, 180, 180));
scrollPanel->AddChild(highestScoreTitle);
ui::Label *highestScore = new ui::Label(ui::Point(12+highestScoreTitle->Size.X, currentY), ui::Point(Size.X-highestScoreTitle->Size.X-16, 15), String::Build(info.highestScore));
ui::Label *highestScore = new ui::Label(ui::Point(13+highestScoreTitle->Size.X, currentY), ui::Point(Size.X-highestScoreTitle->Size.X-24, 15), String::Build(info.highestScore));
highestScore->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
scrollPanel->AddChild(highestScore);
currentY += 2+highestScore->Size.Y;

View File

@ -79,6 +79,7 @@ ServerSaveActivity::ServerSaveActivity(std::unique_ptr<SaveInfo> newSave, OnUplo
nameField->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
nameField->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
nameField->SetActionCallback({ [this] { CheckName(nameField->GetText()); } });
nameField->SetLimit(50);
AddComponent(nameField);
FocusComponent(nameField);

View File

@ -136,6 +136,32 @@ void SearchController::SetPageRelative(int offset)
searchModel->UpdateSaveList(page, searchModel->GetLastQuery());
}
void SearchController::ChangePeriod(int period)
{
switch(period)
{
case 0:
searchModel->SetPeriod(http::allSaves);
break;
case 1:
searchModel->SetPeriod(http::todaySaves);
break;
case 2:
searchModel->SetPeriod(http::weekSaves);
break;
case 3:
searchModel->SetPeriod(http::monthSaves);
break;
case 4:
searchModel->SetPeriod(http::yearSaves);
break;
default:
searchModel->SetPeriod(http::allSaves);
}
searchModel->UpdateSaveList(1, searchModel->GetLastQuery());
}
void SearchController::ChangeSort()
{
if(searchModel->GetSort() == http::sortByDate)

View File

@ -37,6 +37,7 @@ public:
void Refresh();
void SetPage(int page);
void SetPageRelative(int offset);
void ChangePeriod(int period);
void ChangeSort();
void ShowOwn(bool show);
void ShowFavourite(bool show);

View File

@ -11,6 +11,7 @@
#include <cmath>
SearchModel::SearchModel():
currentPeriod(http::allSaves),
currentSort(http::sortByVotes),
currentPage(1),
resultCount(0),
@ -30,11 +31,11 @@ bool SearchModel::GetShowTags()
return showTags;
}
void SearchModel::BeginSearchSaves(int start, int count, String query, http::Sort sort, http::Category category)
void SearchModel::BeginSearchSaves(int start, int count, String query, http::Period period, http::Sort sort, http::Category category)
{
lastError = "";
resultCount = 0;
searchSaves = std::make_unique<http::SearchSavesRequest>(start, count, query.ToUtf8(), sort, category);
searchSaves = std::make_unique<http::SearchSavesRequest>(start, count, query.ToUtf8(), period, sort, category);
searchSaves->Start();
}
@ -95,7 +96,7 @@ bool SearchModel::UpdateSaveList(int pageNumber, String query)
//resultCount = 0;
currentPage = pageNumber;
if(pageNumber == 1 && !showOwn && !showFavourite && currentSort == http::sortByVotes && query == "")
if(pageNumber == 1 && !showOwn && !showFavourite && currentPeriod == http::allSaves && currentSort == http::sortByVotes && query == "")
SetShowTags(true);
else
SetShowTags(false);
@ -120,7 +121,7 @@ bool SearchModel::UpdateSaveList(int pageNumber, String query)
{
category = http::categoryMyOwn;
}
BeginSearchSaves((currentPage-1)*20, 20, lastQuery, currentSort, category);
BeginSearchSaves((currentPage-1)*20, 20, lastQuery, currentPeriod, currentSort, category);
return true;
}
return false;
@ -178,6 +179,7 @@ void SearchModel::AddObserver(SearchView * observer)
observers.push_back(observer);
observer->NotifySaveListChanged(this);
observer->NotifyPageChanged(this);
observer->NotifyPeriodChanged(this);
observer->NotifySortChanged(this);
observer->NotifyShowOwnChanged(this);
observer->NotifyTagListChanged(this);
@ -258,6 +260,15 @@ void SearchModel::notifyPageChanged()
}
}
void SearchModel::notifyPeriodChanged()
{
for (size_t i = 0; i < observers.size(); i++)
{
SearchView* cObserver = observers[i];
cObserver->NotifyPeriodChanged(this);
}
}
void SearchModel::notifySortChanged()
{
for (size_t i = 0; i < observers.size(); i++)
@ -296,7 +307,7 @@ void SearchModel::notifySelectedChanged()
int SearchModel::GetPageCount()
{
if (!showOwn && !showFavourite && currentSort == http::sortByVotes && lastQuery == "")
if (!showOwn && !showFavourite && currentPeriod == http::allSaves && currentSort == http::sortByVotes && lastQuery == "")
return std::max(1, (int)(ceil(resultCount/20.0f))+1); //add one for front page (front page saves are repeated twice)
else
return std::max(1, (int)(ceil(resultCount/20.0f)));

View File

@ -18,7 +18,7 @@ class SearchModel
{
private:
std::unique_ptr<http::SearchSavesRequest> searchSaves;
void BeginSearchSaves(int start, int count, String query, http::Sort sort, http::Category category);
void BeginSearchSaves(int start, int count, String query, http::Period period, http::Sort sort, http::Category category);
std::vector<std::unique_ptr<SaveInfo>> EndSearchSaves();
void BeginGetTags(int start, int count, String query);
@ -26,6 +26,7 @@ private:
std::unique_ptr<http::SearchTagsRequest> getTags;
std::unique_ptr<SaveInfo> loadedSave;
http::Period currentPeriod;
http::Sort currentSort;
String lastQuery;
String lastError;
@ -42,6 +43,7 @@ private:
void notifyTagListChanged();
void notifySelectedChanged();
void notifyPageChanged();
void notifyPeriodChanged();
void notifySortChanged();
void notifyShowOwnChanged();
void notifyShowFavouriteChanged();
@ -61,6 +63,8 @@ public:
int GetPageCount();
int GetPageNum() { return currentPage; }
String GetLastQuery() { return lastQuery; }
void SetPeriod(http::Period period) { if(!searchSaves) { currentPeriod = period; } notifyPeriodChanged(); }
http::Period GetPeriod() { return currentPeriod; }
void SetSort(http::Sort sort) { if(!searchSaves) { currentSort = sort; } notifySortChanged(); }
http::Sort GetSort() { return currentSort; }
void SetShowOwn(bool show) { if(!searchSaves) { if(show!=showOwn) { showOwn = show; } } notifyShowOwnChanged(); }

View File

@ -9,6 +9,7 @@
#include "gui/interface/RichLabel.h"
#include "gui/interface/Textbox.h"
#include "gui/interface/Spinner.h"
#include "gui/interface/DropDown.h"
#include "PowderToySDL.h"
#include "graphics/Graphics.h"
#include "SimulationConfig.h"
@ -43,13 +44,25 @@ SearchView::SearchView():
AddComponent(pageCountLabel);
AddComponent(pageTextbox);
searchField = new ui::Textbox(ui::Point(60, 10), ui::Point(WINDOWW-238, 17), "", "[search]");
searchField = new ui::Textbox(ui::Point(60, 10), ui::Point(WINDOWW-283, 17), "", "[search]");
searchField->Appearance.icon = IconSearch;
searchField->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
searchField->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
searchField->SetActionCallback({ [this] { doSearch(); } });
searchField->SetLimit(100);
FocusComponent(searchField);
dateRange = new ui::DropDown(ui::Point(WINDOWW-185, 10), ui::Point(36, 17));
dateRange->SetActionCallback({ [this] { c->ChangePeriod(dateRange->GetOption().second); } });
dateRange->AddOption({"All", 0});
dateRange->AddOption({"Day", 1});
dateRange->AddOption({"Week", 2});
dateRange->AddOption({"Month", 3});
dateRange->AddOption({"Year", 4});
dateRange->Appearance.HorizontalAlign = ui::Appearance::AlignCentre;
dateRange->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
AddComponent(dateRange);
sortButton = new ui::Button(ui::Point(WINDOWW-140, 10), ui::Point(61, 17), "Sort");
sortButton->SetIcon(IconVoteSort);
sortButton->SetTogglable(true);
@ -201,6 +214,11 @@ void SearchView::Search(String query)
c->DoSearch(query, true);
}
void SearchView::NotifyPeriodChanged(SearchModel * sender)
{
dateRange->SetOption(sender->GetPeriod());
}
void SearchView::NotifySortChanged(SearchModel * sender)
{
if(sender->GetSort() == http::sortByVotes)
@ -489,7 +507,7 @@ void SearchView::NotifySaveListChanged(SearchModel * sender)
loadingSpinner->Visible = false;
if (!errorLabel)
{
errorLabel = new ui::Label(ui::Point((WINDOWW/2)-100, (WINDOWH/2)-6), ui::Point(200, 12), "Error");
errorLabel = new ui::Label(ui::Point(0, (WINDOWH/2)-6), ui::Point(WINDOWW, 12), "Error");
AddComponent(errorLabel);
}
if (!sender->GetSavesLoaded())

View File

@ -11,6 +11,7 @@ namespace ui
class Label;
class Spinner;
class Textbox;
class DropDown;
}
class SearchModel;
@ -32,6 +33,7 @@ private:
ui::Label * pageCountLabel;
ui::Label * tagsLabel;
ui::RichLabel * motdLabel = nullptr;
ui::DropDown * dateRange;
ui::Button * sortButton;
ui::Button * ownButton;
ui::Spinner * loadingSpinner;
@ -52,6 +54,7 @@ public:
void NotifySaveListChanged(SearchModel * sender);
void NotifySelectedChanged(SearchModel * sender);
void NotifyPageChanged(SearchModel * sender);
void NotifyPeriodChanged(SearchModel * sender);
void NotifySortChanged(SearchModel * sender);
void NotifyShowOwnChanged(SearchModel * sender);
void NotifyShowFavouriteChanged(SearchModel * sender);

View File

@ -30,6 +30,7 @@ TagsView::TagsView():
tagInput->Appearance.icon = IconTag;
tagInput->Appearance.HorizontalAlign = ui::Appearance::AlignLeft;
tagInput->Appearance.VerticalAlign = ui::Appearance::AlignMiddle;
tagInput->SetLimit(16);
AddComponent(tagInput);
FocusComponent(tagInput);

View File

@ -151,7 +151,7 @@ void UpdateActivity::NotifyError(Task * sender)
new ConfirmPrompt("Autoupdate failed", sb.Build(), { [this] {
if constexpr (!USE_UPDATESERVER)
{
Platform::OpenURI(ByteString(SCHEME) + "powdertoy.co.uk/Download.html");
Platform::OpenURI(ByteString::Build(SCHEME, SERVER, "/Download.html"));
}
Exit();
}, [this] { Exit(); } });

View File

@ -856,7 +856,7 @@ void LuaElements::Open(lua_State *L)
LCONST(TYPE_GAS);
LCONST(TYPE_ENERGY);
LCONST(PROP_CONDUCTS);
LCONST(PROP_BLACK);
LCONST(PROP_PHOTPASS);
LCONST(PROP_NEUTPENETRATE);
LCONST(PROP_NEUTABSORB);
LCONST(PROP_NEUTPASS);

View File

@ -50,7 +50,10 @@ static int beginMessageBox(lua_State *L)
auto message = PickIfType(L, 2, String("Message"));
auto large = PickIfType(L, 3, false);
auto cb = std::make_shared<LuaSmartRef>(); // * Bind to main lua state (might be different from L).
cb->Assign(L, lua_gettop(L));
if (lua_gettop(L))
{
cb->Assign(L, lua_gettop(L));
}
new InformationMessage(title, message, large, { [cb]() {
auto *lsi = GetLSI();
auto L = lsi->L;
@ -74,7 +77,10 @@ static int beginThrowError(lua_State *L)
{
auto errorMessage = PickIfType(L, 1, String("Error text"));
auto cb = std::make_shared<LuaSmartRef>(); // * Bind to main lua state (might be different from L).
cb->Assign(L, lua_gettop(L));
if (lua_gettop(L))
{
cb->Assign(L, lua_gettop(L));
}
new ErrorMessage("Error", errorMessage, { [cb]() {
auto *lsi = GetLSI();
auto L = lsi->L;
@ -101,7 +107,10 @@ static int beginInput(lua_State *L)
auto text = PickIfType(L, 3, String(""));
auto shadow = PickIfType(L, 4, String(""));
auto cb = std::make_shared<LuaSmartRef>(); // * Bind to main lua state (might be different from L).
cb->Assign(L, lua_gettop(L));
if (lua_gettop(L))
{
cb->Assign(L, lua_gettop(L));
}
auto handle = [cb](std::optional<String> input) {
auto *lsi = GetLSI();
auto L = lsi->L;
@ -140,7 +149,10 @@ static int beginConfirm(lua_State *L)
auto message = PickIfType(L, 2, String("Message"));
auto buttonText = PickIfType(L, 3, String("Confirm"));
auto cb = std::make_shared<LuaSmartRef>(); // * Bind to main lua state (might be different from L).
cb->Assign(L, lua_gettop(L));
if (lua_gettop(L))
{
cb->Assign(L, lua_gettop(L));
}
auto handle = [cb](int result) {
auto *lsi = GetLSI();
auto L = lsi->L;

View File

@ -1960,9 +1960,11 @@ void LuaSimulation::Open(lua_State *L)
};
lua_newtable(L);
luaL_register(L, NULL, reg);
#define LCONST(v) lua_pushinteger(L, int(v)); lua_setfield(L, -2, #v)
#define LCONSTF(v) lua_pushnumber(L, float(v)); lua_setfield(L, -2, #v)
#define LCONSTAS(k, v) lua_pushinteger(L, int(v)); lua_setfield(L, -2, k)
LCONST(CELL);
LCONST(XCELLS);
LCONST(YCELLS);
@ -1987,6 +1989,7 @@ void LuaSimulation::Open(lua_State *L)
LCONST(ISTP);
LCONSTF(CFDS);
LCONSTF(MAX_VELOCITY);
LCONST(TOOL_HEAT);
LCONST(TOOL_COOL);
LCONST(TOOL_VAC);
@ -1996,6 +1999,7 @@ void LuaSimulation::Open(lua_State *L)
LCONST(TOOL_MIX);
LCONST(TOOL_CYCL);
LCONSTAS("TOOL_WIND", sd.tools.size());
LCONST(DECO_DRAW);
LCONST(DECO_CLEAR);
LCONST(DECO_ADD);
@ -2003,32 +2007,45 @@ void LuaSimulation::Open(lua_State *L)
LCONST(DECO_MULTIPLY);
LCONST(DECO_DIVIDE);
LCONST(DECO_SMUDGE);
LCONST(FLAG_STAGNANT);
LCONST(FLAG_SKIPMOVE);
LCONST(FLAG_MOVABLE);
LCONST(FLAG_PHOTDECO);
LCONST(PMAPBITS);
LCONST(PMAPMASK);
LCONST(BRUSH_CIRCLE);
LCONST(BRUSH_SQUARE);
LCONST(BRUSH_TRIANGLE);
LCONST(NUM_DEFAULTBRUSHES);
LCONSTAS("NUM_BRUSHES", lsi->gameModel->BrushListSize());
LCONST(EDGE_VOID);
LCONST(EDGE_SOLID);
LCONST(EDGE_LOOP);
LCONST(NUM_EDGEMODES);
LCONST(AIR_ON);
LCONST(AIR_PRESSUREOFF);
LCONST(AIR_VELOCITYOFF);
LCONST(AIR_OFF);
LCONST(AIR_NOUPDATE);
LCONST(NUM_AIRMODES);
LCONST(GRAV_VERTICAL);
LCONST(GRAV_OFF);
LCONST(GRAV_RADIAL);
LCONST(GRAV_CUSTOM);
LCONST(NUM_GRAVMODES);
LCONST(DECOSPACE_SRGB);
LCONST(DECOSPACE_LINEAR);
LCONST(DECOSPACE_GAMMA22);
LCONST(DECOSPACE_GAMMA18);
LCONST(NUM_DECOSPACES);
{
lua_newtable(L);
for (int i = 0; i < UI_WALLCOUNT; i++)

View File

@ -9,6 +9,7 @@ elem.FLAG_MOVABLE = sim.FLAG_MOVABLE
elem.FLAG_PHOTDECO = sim.FLAG_PHOTDECO
elem.FLAG_SKIPMOVE = sim.FLAG_SKIPMOVE
elem.FLAG_STAGNANT = sim.FLAG_STAGNANT
elem.PROP_BLACK = 0
elem.PROP_DRAWONCTYPE = 0
elem.ST_GAS = 0
elem.ST_LIQUID = 0

View File

@ -20,6 +20,11 @@ void Prefs::Read()
Json::CharReaderBuilder rbuilder;
std::unique_ptr<Json::CharReader> const reader(rbuilder.newCharReader());
ByteString errs;
if (!data.size())
{
std::cerr << "no json data" << std::endl;
return;
}
if (!reader->parse(&data[0], &data[0] + data.size(), &root, &errs))
{
std::cerr << errs << std::endl;

View File

@ -17,7 +17,7 @@ constexpr auto TYPE_GAS = UINT32_C(0x00000008); //8 Gases (Includes p
constexpr auto TYPE_ENERGY = UINT32_C(0x00000010); //16 Energy (Thunder, Light, Neutrons etc.)
constexpr auto STATE_FLAGS = UINT32_C(0x0000001F);
constexpr auto PROP_CONDUCTS = UINT32_C(0x00000020); //32 Conducts electricity
constexpr auto PROP_BLACK = UINT32_C(0x00000040); //64 Absorbs Photons (not currently implemented or used, a photwl attribute might be better)
constexpr auto PROP_PHOTPASS = UINT32_C(0x00000040); //64 Photons pass through (may refract as in glass)
constexpr auto PROP_NEUTPENETRATE = UINT32_C(0x00000080); //128 Penetrated by neutrons
constexpr auto PROP_NEUTABSORB = UINT32_C(0x00000100); //256 Absorbs neutrons, reflect is default
constexpr auto PROP_NEUTPASS = UINT32_C(0x00000200); //512 Neutrons pass through, such as with glass

View File

@ -186,16 +186,14 @@ void SimulationData::init_can_move()
if (elements[movingType].Properties & TYPE_PART)
can_move[movingType][PT_SAWD] = 0;
}
//a list of lots of things PHOT can move through
// TODO: replace with property
for (destinationType = 0; destinationType < PT_NUM; destinationType++)
{
if (destinationType == PT_GLAS || destinationType == PT_PHOT || destinationType == PT_FILT || destinationType == PT_INVIS
|| destinationType == PT_CLNE || destinationType == PT_PCLN || destinationType == PT_BCLN || destinationType == PT_PBCN
|| destinationType == PT_WATR || destinationType == PT_DSTW || destinationType == PT_SLTW || destinationType == PT_GLOW
|| destinationType == PT_ISOZ || destinationType == PT_ISZS || destinationType == PT_QRTZ || destinationType == PT_PQRT
|| destinationType == PT_H2 || destinationType == PT_BGLA || destinationType == PT_C5 || destinationType == PT_RSST)
//a list of lots of things PHOT can move through
if (elements[destinationType].Properties & PROP_PHOTPASS)
can_move[PT_PHOT][destinationType] = 2;
//Things PROT and GRVT cannot move through
if (destinationType != PT_DMND && destinationType != PT_INSL && destinationType != PT_VOID && destinationType != PT_PVOD && destinationType != PT_VIBR && destinationType != PT_BVBR && destinationType != PT_PRTI && destinationType != PT_PRTO)
{
can_move[PT_PROT][destinationType] = 2;

View File

@ -31,7 +31,7 @@ void Element::Element_BCLN()
HeatConduct = 251;
Description = "Breakable Clone.";
Properties = TYPE_SOLID | PROP_LIFE_DEC | PROP_LIFE_KILL_DEC | PROP_NOCTYPEDRAW;
Properties = TYPE_SOLID | PROP_PHOTPASS | PROP_LIFE_DEC | PROP_LIFE_KILL_DEC | PROP_NOCTYPEDRAW;
CarriesTypeIn = 1U << FIELD_CTYPE;
LowPressure = IPL;

View File

@ -29,7 +29,7 @@ void Element::Element_BGLA()
HeatConduct = 150;
Description = "Broken Glass, heavy particles formed when glass breaks under pressure. Meltable. Bagels.";
Properties = TYPE_PART | PROP_NEUTPASS | PROP_HOT_GLOW;
Properties = TYPE_PART | PROP_NEUTPASS | PROP_PHOTPASS | PROP_HOT_GLOW;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -32,7 +32,7 @@ void Element::Element_C5()
HeatConduct = 88;
Description = "Cold explosive, set off by anything cold.";
Properties = TYPE_SOLID | PROP_NEUTPENETRATE | PROP_LIFE_DEC;
Properties = TYPE_SOLID | PROP_NEUTPENETRATE | PROP_PHOTPASS | PROP_LIFE_DEC;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -31,7 +31,7 @@ void Element::Element_CLNE()
HeatConduct = 251;
Description = "Clone. Duplicates any particles it touches.";
Properties = TYPE_SOLID | PROP_NOCTYPEDRAW;
Properties = TYPE_SOLID | PROP_PHOTPASS | PROP_NOCTYPEDRAW;
CarriesTypeIn = 1U << FIELD_CTYPE;
LowPressure = IPL;

View File

@ -33,7 +33,7 @@ void Element::Element_DSTW()
LatentHeat = 7500;
Description = "Distilled water, does not conduct electricity.";
Properties = TYPE_LIQUID|PROP_NEUTPASS;
Properties = TYPE_LIQUID | PROP_NEUTPASS | PROP_PHOTPASS;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -33,7 +33,7 @@ void Element::Element_FILT()
HeatConduct = 251;
Description = "Filter for photons, changes the color.";
Properties = TYPE_SOLID | PROP_NOAMBHEAT | PROP_LIFE_DEC;
Properties = TYPE_SOLID | PROP_PHOTPASS | PROP_NOAMBHEAT | PROP_LIFE_DEC;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -32,7 +32,7 @@ void Element::Element_GLAS()
HeatConduct = 150;
Description = "Glass. Meltable. Shatters under pressure, and refracts photons.";
Properties = TYPE_SOLID | PROP_NEUTPASS | PROP_HOT_GLOW | PROP_SPARKSETTLE;
Properties = TYPE_SOLID | PROP_NEUTPASS | PROP_PHOTPASS | PROP_HOT_GLOW | PROP_SPARKSETTLE;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -33,7 +33,7 @@ void Element::Element_GLOW()
HeatConduct = 44;
Description = "Glow, Glows under pressure.";
Properties = TYPE_LIQUID | PROP_LIFE_DEC;
Properties = TYPE_LIQUID | PROP_PHOTPASS | PROP_LIFE_DEC;
LowPressure = IPL;
LowPressureTransition = NT;
@ -59,17 +59,21 @@ static int update(UPDATE_FUNC_ARGS)
auto r = pmap[y+ry][x+rx];
if (!r)
continue;
if (TYP(r)==PT_WATR && sim->rng.chance(1, 400))
{
sim->kill_part(i);
sim->part_change_type(ID(r),x+rx,y+ry,PT_DEUT);
parts[ID(r)].life = 10;
return 1;
}
else if (TYP(r) == PT_GEL) //GLOW + GEL = RSST
{
sim->kill_part(i);
sim->part_change_type(ID(r),x+rx,y+ry,PT_RSST);
parts[ID(r)].tmp = 0;
return 1;
}
}

View File

@ -31,7 +31,7 @@ void Element::Element_H2()
HeatConduct = 251;
Description = "Hydrogen. Combusts with OXYG to make WATR. Undergoes fusion at high temperature and pressure.";
Properties = TYPE_GAS;
Properties = TYPE_GAS | PROP_PHOTPASS;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -34,6 +34,7 @@ void Element::Element_ICEI()
Description = "Crushes under pressure. Cools down air.";
Properties = TYPE_SOLID|PROP_LIFE_DEC|PROP_NEUTPASS;
CarriesTypeIn = 1U << FIELD_CTYPE;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -32,7 +32,7 @@ void Element::Element_INVIS()
HeatConduct = 164;
Description = "Invisible to particles while under pressure.";
Properties = TYPE_SOLID | PROP_NEUTPASS;
Properties = TYPE_SOLID | PROP_NEUTPASS | PROP_PHOTPASS;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -32,7 +32,7 @@ void Element::Element_ISOZ()
HeatConduct = 29;
Description = "Isotope-Z. Radioactive liquid, decays into photons when touching PHOT or under negative pressure.";
Properties = TYPE_LIQUID|PROP_NEUTPENETRATE;
Properties = TYPE_LIQUID | PROP_NEUTPENETRATE | PROP_PHOTPASS;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -32,7 +32,7 @@ void Element::Element_ISZS()
HeatConduct = 251;
Description = "Solid form of ISOZ, slowly decays into PHOT.";
Properties = TYPE_SOLID;
Properties = TYPE_SOLID | PROP_PHOTPASS;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -54,6 +54,8 @@ void Element::Element_NEUT()
static int update(UPDATE_FUNC_ARGS)
{
auto &sd = SimulationData::CRef();
auto &elements = sd.elements;
unsigned int pressureFactor = 3 + (int)sim->pv[y/CELL][x/CELL];
for (int rx = -1; rx <= 1; rx++)
{
@ -178,7 +180,7 @@ static int update(UPDATE_FUNC_ARGS)
sim->create_part(ID(r), x, y, ct_under);
//If there's a correct tmp set, use it for ctype
if(tmp_under > 0 && ct_under < PT_NUM)
if((tmp_under > 0) && (tmp_under < PT_NUM) && (elements[ct_under].CarriesTypeIn & (1U << FIELD_CTYPE)))
parts[ID(r)].ctype = tmp_under;
}
else

View File

@ -33,7 +33,7 @@ void Element::Element_PBCN()
HeatConduct = 251;
Description = "Powered breakable clone.";
Properties = TYPE_SOLID | PROP_NOCTYPEDRAW;
Properties = TYPE_SOLID | PROP_PHOTPASS | PROP_NOCTYPEDRAW;
CarriesTypeIn = 1U << FIELD_CTYPE;
LowPressure = IPL;

View File

@ -33,7 +33,7 @@ void Element::Element_PCLN()
HeatConduct = 251;
Description = "Powered clone. When activated, duplicates any particles it touches.";
Properties = TYPE_SOLID | PROP_NOCTYPEDRAW;
Properties = TYPE_SOLID | PROP_PHOTPASS | PROP_NOCTYPEDRAW;
CarriesTypeIn = 1U << FIELD_CTYPE;
LowPressure = IPL;

View File

@ -36,7 +36,7 @@ void Element::Element_PHOT()
HeatConduct = 251;
Description = "Photons. Refracts through glass, scattered by quartz, and color-changed by different elements. Ignites flammable materials.";
Properties = TYPE_ENERGY|PROP_LIFE_DEC|PROP_LIFE_KILL_DEC;
Properties = TYPE_ENERGY | PROP_PHOTPASS | PROP_LIFE_DEC | PROP_LIFE_KILL_DEC;
LowPressure = IPL;
LowPressureTransition = NT;
@ -57,6 +57,9 @@ void Element::Element_PHOT()
static int update(UPDATE_FUNC_ARGS)
{
auto &sd = SimulationData::CRef();
auto &elements = sd.elements;
if (!(parts[i].ctype&0x3FFFFFFF)) {
sim->kill_part(i);
return 1;
@ -121,7 +124,7 @@ static int update(UPDATE_FUNC_ARGS)
sim->create_part(ID(r), x, y, ct_under);
//If there's a correct tmp set, use it for ctype
if(tmp_under > 0 && ct_under < PT_NUM)
if((tmp_under > 0) && (tmp_under < PT_NUM) && (elements[ct_under].CarriesTypeIn & (1U << FIELD_CTYPE)))
parts[ID(r)].ctype = tmp_under;
}
else

View File

@ -32,7 +32,7 @@ void Element::Element_PQRT()
HeatConduct = 3;
Description = "Powdered quartz, broken form of QRTZ.";
Properties = TYPE_PART| PROP_HOT_GLOW;
Properties = TYPE_PART | PROP_PHOTPASS | PROP_HOT_GLOW;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -238,7 +238,7 @@ static int update(UPDATE_FUNC_ARGS)
break;
case PT_RSST: // RSST -> BIZR
sim->part_change_type(ID(r), x + rx, y + ry, PT_BIZR);
sim->create_part(ID(r), x + rx, y + ry, PT_BIZR);
break;
}
}

View File

@ -32,7 +32,7 @@ void Element::Element_QRTZ()
HeatConduct = 3;
Description = "Quartz, breakable mineral. Conducts but becomes brittle at lower temperatures.";
Properties = TYPE_SOLID|PROP_HOT_GLOW|PROP_LIFE_DEC;
Properties = TYPE_SOLID | PROP_PHOTPASS | PROP_HOT_GLOW | PROP_LIFE_DEC;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -33,6 +33,7 @@ void Element::Element_RSSS()
Description = "Solidified resist. Blocks pressure and insulates electricity. Liquefies on contact with neutrons.";
Properties = TYPE_SOLID|PROP_NEUTPASS;
CarriesTypeIn = (1U << FIELD_CTYPE) | (1U << FIELD_TMP);
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -32,7 +32,8 @@ void Element::Element_RSST()
HeatConduct = 55;
Description = "Resist. Solidifies on contact with photons, is destroyed by electrons and spark.";
Properties = TYPE_LIQUID|PROP_CONDUCTS|PROP_LIFE_DEC|PROP_NEUTPASS;
Properties = TYPE_LIQUID | PROP_CONDUCTS | PROP_LIFE_DEC | PROP_NEUTPASS | PROP_PHOTPASS;
CarriesTypeIn = (1U << FIELD_CTYPE) | (1U << FIELD_TMP);
LowPressure = IPL;
LowPressureTransition = NT;
@ -60,7 +61,7 @@ int update(UPDATE_FUNC_ARGS)
// RSST + GUNP = FIRW
if(TYP(r) == PT_GUNP)
{
sim->part_change_type(i, x, y, PT_FIRW);
sim->create_part(i, x, y, PT_FIRW);
sim->kill_part(ID(r));
return 1;
}
@ -68,7 +69,7 @@ int update(UPDATE_FUNC_ARGS)
// RSST + BCOL = FSEP
if(TYP(r) == PT_BCOL)
{
sim->part_change_type(i, x, y, PT_FSEP);
sim->create_part(i, x, y, PT_FSEP);
parts[i].life = 50;
sim->kill_part(ID(r));
return 1;
@ -80,6 +81,13 @@ int update(UPDATE_FUNC_ARGS)
if(parts[ID(r)].ctype != PT_RSST)
parts[i].ctype = parts[ID(r)].ctype;
}
// Set RSST tmp from nearby breakable clone
if((TYP(r) == PT_BCLN) || (TYP(r) == PT_PBCN))
{
if(parts[ID(r)].ctype != PT_RSST)
parts[i].tmp = parts[ID(r)].ctype;
}
}
}

View File

@ -32,7 +32,7 @@ void Element::Element_SLTW()
LatentHeat = 7500;
Description = "Saltwater, conducts electricity, difficult to freeze.";
Properties = TYPE_LIQUID|PROP_CONDUCTS|PROP_LIFE_DEC|PROP_NEUTPENETRATE;
Properties = TYPE_LIQUID | PROP_CONDUCTS | PROP_LIFE_DEC | PROP_NEUTPENETRATE | PROP_PHOTPASS;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -35,6 +35,7 @@ void Element::Element_SNOW()
Description = "Light particles. Created when ICE breaks under pressure.";
Properties = TYPE_PART|PROP_NEUTPASS;
CarriesTypeIn = 1U << FIELD_CTYPE;
LowPressure = IPL;
LowPressureTransition = NT;

View File

@ -33,7 +33,7 @@ void Element::Element_WATR()
LatentHeat = 7500;
Description = "Water. Conducts electricity, freezes, and extinguishes fires.";
Properties = TYPE_LIQUID|PROP_CONDUCTS|PROP_LIFE_DEC|PROP_NEUTPASS;
Properties = TYPE_LIQUID | PROP_CONDUCTS | PROP_LIFE_DEC | PROP_NEUTPASS | PROP_PHOTPASS;
LowPressure = IPL;
LowPressureTransition = NT;