Merge pull request #197 from traintastic/144-add-zone-support

144 add zone support
Dieser Commit ist enthalten in:
Reinder Feenstra 2025-08-27 22:42:04 +02:00 committet von GitHub
Commit fabd0b2a17
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: B5690EEEBB952194
73 geänderte Dateien mit 4231 neuen und 102 gelöschten Zeilen

Datei anzeigen

@ -92,7 +92,9 @@
<file>board_tile.rail.nx_button.svg</file>
<file>board_tile.misc.switch.svg</file>
<file>board_tile.misc.label.svg</file>
<file>zone.svg</file>
<file>clear_persistent_variables.svg</file>
<file>highlight_zone.svg</file>
<file>swap.svg</file>
<file>circle/add.svg</file>
</qresource>

Datei anzeigen

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="96"
height="96"
viewBox="0 0 25.399999 25.400001"
version="1.1"
id="svg8"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="highlight_zone.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="43.035714"
inkscape:cy="51.964286"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1335"
inkscape:window-height="1015"
inkscape:window-x="585"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:pagecheckerboard="0">
<inkscape:grid
type="xygrid"
id="grid3713"
spacingx="0.26458333"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-271.59998)">
<rect
style="fill:#555555;stroke:#ffffff;stroke-width:2.11666667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
id="rect3462"
width="14.816667"
height="14.816663"
x="2.1166666"
y="280.06665"
ry="3.175" />
<path
id="path3640-5-2"
style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.283333,283.24165 v -4.23334"
sodipodi:nodetypes="cc" />
<path
id="path3637-6-7"
style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 19.05,288.53332 h 1.058333 c 1.75895,0 3.175,-1.41605 3.175,-3.175 v -1.05834" />
<path
id="rect2600-2"
style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 8.4666664,277.94997 v -1.05833 c 0,-1.75895 1.4160506,-3.175 3.1750006,-3.175 v 0 H 12.7" />
<path
id="rect2600-3-9"
style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.283331,277.94997 v -1.05833 c 0,-1.75895 -1.41605,-3.175 -3.175,-3.175 v 0 h -1.058333" />
<path
id="path3632-1"
style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.758333,273.71665 h 4.233333"
sodipodi:nodetypes="cc" />
</g>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 4.2 KiB

143
client/gfx/dark/zone.svg Normale Datei
Datei anzeigen

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="96"
height="96"
viewBox="0 0 25.399999 25.400001"
version="1.1"
id="svg8"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="zone.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="43.035714"
inkscape:cy="51.964286"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1335"
inkscape:window-height="1015"
inkscape:window-x="585"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:pagecheckerboard="0">
<inkscape:grid
type="xygrid"
id="grid3713"
spacingx="0.26458333"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-271.59998)">
<path
id="rect2600"
style="fill:none;stroke:#fffffe;stroke-width:8.00001;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 7.9999994 48.000014 L 7.9999994 44.000014 C 7.9999994 37.352015 13.352 32.000014 20.000001 32.000014 L 20.000001 32.000014 L 24.000001 32.000014 "
transform="matrix(0.26458333,0,0,0.26458333,0,271.59998)" />
<path
id="path3640"
style="fill:none;stroke:#fffffe;stroke-width:8.00001;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 8,68.000001 0,-16"
transform="matrix(0.26458333,0,0,0.26458333,0,271.59998)"
sodipodi:nodetypes="cc" />
<path
id="path3637"
style="fill:none;stroke:#fffffe;stroke-width:8.00001;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 24.000001 88.000013 L 20.000001 88.000013 C 13.352 88.000013 7.9999995 82.648012 7.9999995 76.000011 L 7.9999995 72.000012 "
transform="matrix(0.26458333,0,0,0.26458333,0,271.59998)" />
<path
id="rect2600-3"
style="fill:none;stroke:#fffffe;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 16.933331,284.29998 v -1.05833 c 0,-1.75895 -1.41605,-3.175 -3.175,-3.175 v 0 h -1.058333" />
<path
id="path3640-5"
style="fill:none;stroke:#fffffe;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 16.933333,289.59165 0,-4.23334"
sodipodi:nodetypes="cc" />
<path
id="path3637-6"
style="fill:none;stroke:#fffffe;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 12.699998,294.88332 h 1.058333 c 1.75895,0 3.175,-1.41605 3.175,-3.175 v -1.05834" />
<path
id="path3640-5-2"
style="fill:none;stroke:#fffffe;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.283333,283.24165 v -4.23334"
sodipodi:nodetypes="cc" />
<path
id="path3637-6-7"
style="fill:none;stroke:#fffffe;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 19.05,288.53332 h 1.058333 c 1.75895,0 3.175,-1.41605 3.175,-3.175 v -1.05834" />
<path
id="path3632"
style="fill:none;stroke:#fffffe;stroke-width:8.00001;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 28,32 H 44"
transform="matrix(0.26458333,0,0,0.26458333,0,271.59998)"
sodipodi:nodetypes="cc" />
<path
id="rect2600-2"
style="fill:none;stroke:#fffffe;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 8.4666664,277.94997 v -1.05833 c 0,-1.75895 1.4160506,-3.175 3.1750006,-3.175 v 0 H 12.7" />
<path
id="rect2600-3-9"
style="fill:none;stroke:#fffffe;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.283331,277.94997 v -1.05833 c 0,-1.75895 -1.41605,-3.175 -3.175,-3.175 v 0 h -1.058333" />
<path
id="path3632-1"
style="fill:none;stroke:#fffffe;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.758333,273.71665 h 4.233333"
sodipodi:nodetypes="cc" />
<path
id="path3632-5"
style="fill:none;stroke:#fffffe;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 7.4083332,294.88331 H 11.641667"
sodipodi:nodetypes="cc" />
</g>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 6.4 KiB

Datei anzeigen

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="96"
height="96"
viewBox="0 0 25.399999 25.400001"
version="1.1"
id="svg8"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="highlight_zone.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="43.035714"
inkscape:cy="51.964286"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1335"
inkscape:window-height="1015"
inkscape:window-x="585"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:pagecheckerboard="0">
<inkscape:grid
type="xygrid"
id="grid3713"
spacingx="0.26458333"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-271.59998)">
<rect
style="fill:#aaaaaa;stroke:#000000;stroke-width:2.11666667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
id="rect3462"
width="14.816667"
height="14.816663"
x="2.1166666"
y="280.06665"
ry="3.175" />
<path
id="path3640-5-2"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.283333,283.24165 v -4.23334"
sodipodi:nodetypes="cc" />
<path
id="path3637-6-7"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 19.05,288.53332 h 1.058333 c 1.75895,0 3.175,-1.41605 3.175,-3.175 v -1.05834" />
<path
id="rect2600-2"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 8.4666664,277.94997 v -1.05833 c 0,-1.75895 1.4160506,-3.175 3.1750006,-3.175 v 0 H 12.7" />
<path
id="rect2600-3-9"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.283331,277.94997 v -1.05833 c 0,-1.75895 -1.41605,-3.175 -3.175,-3.175 v 0 h -1.058333" />
<path
id="path3632-1"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.758333,273.71665 h 4.233333"
sodipodi:nodetypes="cc" />
</g>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 4.2 KiB

Datei anzeigen

@ -66,7 +66,9 @@
<file>board_tile.rail.nx_button.svg</file>
<file>board_tile.misc.switch.svg</file>
<file>board_tile.misc.label.svg</file>
<file>zone.svg</file>
<file>clear_persistent_variables.svg</file>
<file>highlight_zone.svg</file>
<file>decoder_function.mute.svg</file>
<file>decoder_function.sound.svg</file>
</qresource>

143
client/gfx/light/zone.svg Normale Datei
Datei anzeigen

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="96"
height="96"
viewBox="0 0 25.399999 25.400001"
version="1.1"
id="svg8"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="zone.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="43.035714"
inkscape:cy="51.964286"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1335"
inkscape:window-height="1015"
inkscape:window-x="585"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:pagecheckerboard="0">
<inkscape:grid
type="xygrid"
id="grid3713"
spacingx="0.26458333"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-271.59998)">
<path
id="rect2600"
style="fill:none;stroke:#000000;stroke-width:8.00001;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 7.9999994 48.000014 L 7.9999994 44.000014 C 7.9999994 37.352015 13.352 32.000014 20.000001 32.000014 L 20.000001 32.000014 L 24.000001 32.000014 "
transform="matrix(0.26458333,0,0,0.26458333,0,271.59998)" />
<path
id="path3640"
style="fill:none;stroke:#000000;stroke-width:8.00001;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 8,68.000001 0,-16"
transform="matrix(0.26458333,0,0,0.26458333,0,271.59998)"
sodipodi:nodetypes="cc" />
<path
id="path3637"
style="fill:none;stroke:#000000;stroke-width:8.00001;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 24.000001 88.000013 L 20.000001 88.000013 C 13.352 88.000013 7.9999995 82.648012 7.9999995 76.000011 L 7.9999995 72.000012 "
transform="matrix(0.26458333,0,0,0.26458333,0,271.59998)" />
<path
id="rect2600-3"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 16.933331,284.29998 v -1.05833 c 0,-1.75895 -1.41605,-3.175 -3.175,-3.175 v 0 h -1.058333" />
<path
id="path3640-5"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 16.933333,289.59165 0,-4.23334"
sodipodi:nodetypes="cc" />
<path
id="path3637-6"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 12.699998,294.88332 h 1.058333 c 1.75895,0 3.175,-1.41605 3.175,-3.175 v -1.05834" />
<path
id="path3640-5-2"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.283333,283.24165 v -4.23334"
sodipodi:nodetypes="cc" />
<path
id="path3637-6-7"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 19.05,288.53332 h 1.058333 c 1.75895,0 3.175,-1.41605 3.175,-3.175 v -1.05834" />
<path
id="path3632"
style="fill:none;stroke:#000000;stroke-width:8.00001;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 28,32 H 44"
transform="matrix(0.26458333,0,0,0.26458333,0,271.59998)"
sodipodi:nodetypes="cc" />
<path
id="rect2600-2"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 8.4666664,277.94997 v -1.05833 c 0,-1.75895 1.4160506,-3.175 3.1750006,-3.175 v 0 H 12.7" />
<path
id="rect2600-3-9"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 23.283331,277.94997 v -1.05833 c 0,-1.75895 -1.41605,-3.175 -3.175,-3.175 v 0 h -1.058333" />
<path
id="path3632-1"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.758333,273.71665 h 4.233333"
sodipodi:nodetypes="cc" />
<path
id="path3632-5"
style="fill:none;stroke:#000000;stroke-width:2.11667;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 7.4083332,294.88331 H 11.641667"
sodipodi:nodetypes="cc" />
</g>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 6.4 KiB

Datei anzeigen

@ -0,0 +1,55 @@
/**
* client/src/board/blockhighlight.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "blockhighlight.hpp"
BlockHighlight::BlockHighlight(QObject* parent)
: QObject(parent)
, colorPool{{Color::Fuchsia, Color::Green, Color::Blue, Color::Lime, Color::Purple, Color::Red, Color::Aqua}}
{
}
void BlockHighlight::add(const QString& blockId, Color color)
{
auto& colors = m_blockColors[blockId];
if(!colors.contains(color))
{
colors.append(color);
emit colorsChanged(blockId, colors);
}
}
void BlockHighlight::remove(const QString& blockId, Color color)
{
if(auto it = m_blockColors.find(blockId); it != m_blockColors.end())
{
if(int index = it->indexOf(color); index >= 0)
{
it->remove(index);
emit colorsChanged(blockId, *it);
if(it->isEmpty())
{
m_blockColors.remove(blockId);
}
}
}
}

Datei anzeigen

@ -0,0 +1,60 @@
/**
* client/src/board/blockhighlight.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_BOARD_BLOCKHIGHLIGHT_HPP
#define TRAINTASTIC_CLIENT_BOARD_BLOCKHIGHLIGHT_HPP
#include <QObject>
#include <QString>
#include <QVector>
#include <QMap>
#include <traintastic/enum/color.hpp>
#include "../misc/colorpool.hpp"
class BlockHighlight : public QObject
{
Q_OBJECT
public:
using BlockColors = QMap<QString, QVector<Color>>;
private:
BlockColors m_blockColors;
public:
ColorPool colorPool;
explicit BlockHighlight(QObject* parent);
const BlockColors& blockColors() const
{
return m_blockColors;
}
void add(const QString& blockId, Color color);
void remove(const QString& blockId, Color color);
signals:
void colorsChanged(const QString& blockId, const QVector<Color>& colors);
};
#endif

Datei anzeigen

@ -31,6 +31,8 @@
#include "boardwidget.hpp"
#include "getboardcolorscheme.hpp"
#include "tilepainter.hpp"
#include "blockhighlight.hpp"
#include "../mainwindow.hpp"
#include "../network/board.hpp"
#include "../network/callmethod.hpp"
#include "../network/object.tpp"
@ -76,6 +78,7 @@ BoardAreaWidget::BoardAreaWidget(std::shared_ptr<Board> board, QWidget* parent)
m_boardBottom{m_board->getProperty("bottom")},
m_grid{Grid::Dot},
m_zoomLevel{0},
m_blockHighlight{MainWindow::instance->blockHighlight()},
m_mouseLeftButtonPressed{false},
m_mouseRightButtonPressed{false},
m_mouseMoveAction{MouseMoveAction::None},
@ -99,6 +102,12 @@ BoardAreaWidget::BoardAreaWidget(std::shared_ptr<Board> board, QWidget* parent)
connect(&BoardSettings::instance(), &SettingsBase::changed, this, &BoardAreaWidget::settingsChanged);
connect(&m_blockHighlight, &BlockHighlight::colorsChanged, this,
[this](const QString& /*blockId*/, const QVector<Color>& /*colors*/)
{
update();
});
for(const auto& [l, object] : m_board->tileObjects())
tileObjectAdded(l.x, l.y, object);
@ -731,9 +740,32 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
break;
case TileId::RailBlock:
tilePainter.drawBlock(id, r, a, state & 0x01, state & 0x02, m_board->getTileObject(it.first));
{
auto block = m_board->getTileObject(it.first);
tilePainter.drawBlock(id, r, a, state & 0x01, state & 0x02, block);
if(auto itColors = m_blockHighlight.blockColors().find(block->getPropertyValueString("id"));
itColors != m_blockHighlight.blockColors().end() && !itColors->isEmpty())
{
for(int i = 0; i < itColors->size(); ++i)
{
QColor color = toQColor((*itColors)[i]);
painter.setPen({});
color.setAlphaF(m_colorScheme->blockHighlightAlpha);
painter.setBrush(color);
if(a == TileRotate::Deg0)
{
const auto h = r.height() / itColors->size();
painter.drawRect(r.left(), r.top() + i * h, r.width(), h);
}
else
{
const auto w = r.width() / itColors->size();
painter.drawRect(r.left() + i * w, r.top(), w, r.height());
}
}
}
break;
}
case TileId::RailDirectionControl:
tilePainter.drawDirectionControl(id, r, a, isReserved, getDirectionControlState(it.first));
break;

Datei anzeigen

@ -38,6 +38,8 @@
#include "../network/abstractproperty.hpp"
#include "../network/objectptr.hpp"
class BoardWidget;
class BlockHighlight;
class Board;
class BoardAreaWidget : public QWidget
@ -74,6 +76,8 @@ class BoardAreaWidget : public QWidget
Grid m_grid;
int m_zoomLevel;
BlockHighlight& m_blockHighlight;
bool m_mouseLeftButtonPressed;
TileLocation m_mouseLeftButtonPressedTileLocation;
bool m_mouseRightButtonPressed;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021,2023 Reinder Feenstra
* Copyright (C) 2021,2023-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -42,6 +42,7 @@ const BoardColorScheme BoardColorScheme::dark = {
/*.turnoutState =*/ {Qt::blue},
/*.decouplerDeactivated =*/ {0x10, 0x10, 0x10},
/*.decouplerActivated =*/ {0x00, 0xBF, 0xFF},
/*.blockHighlightAlpha =*/ 0.2,
};
const BoardColorScheme BoardColorScheme::light = {
@ -64,4 +65,5 @@ const BoardColorScheme BoardColorScheme::light = {
/*.turnoutState =*/ {Qt::cyan},
/*.decouplerDeactivated =*/ {0xF5, 0xF5, 0xF5},
/*.decouplerActivated =*/ {0x00, 0xBF, 0xFF},
/*.blockHighlightAlpha =*/ 0.3,
};

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021,2023 Reinder Feenstra
* Copyright (C) 2021,2023-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -49,6 +49,7 @@ struct BoardColorScheme
const QColor turnoutState;
const QColor decouplerDeactivated;
const QColor decouplerActivated;
const qreal blockHighlightAlpha;
};
#endif

Datei anzeigen

@ -39,6 +39,7 @@
#include <traintastic/utils/standardpaths.hpp>
#include "mdiarea.hpp"
#include "mainwindow/mainwindowstatusbar.hpp"
#include "board/blockhighlight.hpp"
#include "clock/clock.hpp"
#include "dialog/connectdialog.hpp"
#include "settings/settingsdialog.hpp"
@ -111,6 +112,7 @@ MainWindow::MainWindow(QWidget* parent) :
m_mdiArea{new MdiArea(m_splitter)},
m_statusBar{new MainWindowStatusBar(*this)},
m_serverLog{nullptr},
m_blockHighlight{new BlockHighlight(this)},
m_toolbar{new QToolBar(this)}
{
instance = this;
@ -406,6 +408,14 @@ MainWindow::MainWindow(QWidget* parent) :
menu->addAction(Locale::tr("world:outputs") + "...", [this](){ showObject("world.outputs", Locale::tr("world:outputs")); });
menu->addAction(Locale::tr("hardware:identifications") + "...", [this](){ showObject("world.identifications", Locale::tr("hardware:identifications")); });
boardsAction = m_menuObjects->addAction(Theme::getIcon("board"), Locale::tr("world:boards") + "...", [this](){ showObject("world.boards", Locale::tr("world:boards")); });
m_menuObjects->addAction(
Theme::getIcon("zone"),
Locale::tr("world:zones") + "...",
[this]()
{
showObject("world.zones", Locale::tr("world:zones"));
}
);
m_menuObjects->addAction(Theme::getIcon("clock"), Locale::tr("world:clock") + "...", [this](){ showObject("world.clock", Locale::tr("world:clock")); });
trainsAction = m_menuObjects->addAction(Theme::getIcon("train"), Locale::tr("world:trains") + "...",
[this]()

Datei anzeigen

@ -47,6 +47,7 @@ class IntroductionWizard;
class AddInterfaceWizard;
class NewBoardWizard;
class WorldListDialog;
class BlockHighlight;
class MainWindow final : public QMainWindow
{
@ -71,6 +72,7 @@ class MainWindow final : public QMainWindow
QMdiSubWindow* m_clockWindow = nullptr;
QMap<QString, SubWindow*> m_subWindows;
QMdiSubWindow* m_trainAndRailVehiclesSubWindow = nullptr;
BlockHighlight* m_blockHighlight;
// Main menu:
QAction* m_actionConnectToServer;
QAction* m_actionDisconnectFromServer;
@ -138,6 +140,11 @@ class MainWindow final : public QMainWindow
MainWindow(QWidget *parent = nullptr);
~MainWindow() final;
BlockHighlight& blockHighlight()
{
return *m_blockHighlight;
}
const std::shared_ptr<Connection>& connection() { return m_connection; }
const ObjectPtr& world() const;

Datei anzeigen

@ -0,0 +1,57 @@
/**
* client/src/misc/colorpool.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "colorpool.hpp"
ColorPool::ColorPool(std::initializer_list<Color> colors)
{
m_colors.reserve(colors.size());
for(auto color : colors)
{
m_colors.emplace_back(std::make_pair(color, false));
}
}
Color ColorPool::aquire()
{
for(auto& item : m_colors)
{
if(!item.second)
{
item.second = true;
return item.first;
}
}
return Color::None;
}
void ColorPool::release(Color color)
{
for(auto& item: m_colors)
{
if(item.first == color)
{
item.second = false;
break;
}
}
}

Datei anzeigen

@ -1,9 +1,9 @@
/**
* server/test/main.cpp
* client/src/misc/colorpool.hpp
*
* This file is part of the traintastic test suite.
* This file is part of the traintastic source code.
*
* Copyright (C) 2021 Reinder Feenstra
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -19,3 +19,25 @@
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_MISC_COLORPOOL_HPP
#define TRAINTASTIC_CLIENT_MISC_COLORPOOL_HPP
#include <initializer_list>
#include <vector>
#include <utility>
#include <traintastic/enum/color.hpp>
class ColorPool
{
private:
std::vector<std::pair<Color, bool>> m_colors;
public:
explicit ColorPool(std::initializer_list<Color> colors);
Color aquire();
void release(Color color);
};
#endif

Datei anzeigen

@ -69,6 +69,8 @@ QIcon Theme::getIconForClassId(const QString& classId)
return getIcon("clock");
else if(classId == "train" || classId == "list.train")
return getIcon("train");
else if(classId == "zone" || classId == "list.zone")
return getIcon("zone");
else
return QIcon();
}

Datei anzeigen

@ -26,6 +26,7 @@
#include "objectlist/interfacelistwidget.hpp"
#include "objectlist/throttleobjectlistwidget.hpp"
#include "objectlist/trainlistwidget.hpp"
#include "objectlist/zoneblocklistwidget.hpp"
#include "object/luascripteditwidget.hpp"
#include "object/objecteditwidget.hpp"
#include "object/itemseditwidget.hpp"
@ -70,6 +71,10 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
{
return new TrainListWidget(object, parent);
}
if(classId == "list.zone_block")
{
return new ZoneBlockListWidget(object, parent);
}
else if(object->classId().startsWith("list."))
return new ObjectListWidget(object, parent);
else if(classId == "lua.script")

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023 Reinder Feenstra
* Copyright (C) 2019-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -53,20 +53,7 @@ ListWidget::ListWidget(const ObjectPtr& object, QWidget* parent)
if(tableModel)
{
m_requestId = Connection::invalidRequestId;
m_tableWidget->setTableModel(tableModel);
connect(m_tableWidget, &TableWidget::doubleClicked, this,
[this](const QModelIndex& index)
{
tableDoubleClicked(index);
});
connect(m_tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this,
[this](const QItemSelection&, const QItemSelection&)
{
tableSelectionChanged();
});
tableSelectionChanged();
setTableModel(tableModel);
delete spinner;
}
else if(error)
@ -84,3 +71,19 @@ ListWidget::~ListWidget()
{
object()->connection()->cancelRequest(m_requestId);
}
void ListWidget::setTableModel(const TableModelPtr& tableModel)
{
m_tableWidget->setTableModel(tableModel);
connect(m_tableWidget, &TableWidget::doubleClicked, this,
[this](const QModelIndex& index)
{
tableDoubleClicked(index);
});
connect(m_tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this,
[this](const QItemSelection&, const QItemSelection&)
{
tableSelectionChanged();
});
tableSelectionChanged();
}

Datei anzeigen

@ -25,6 +25,7 @@
#include <QWidget>
#include "../../network/objectptr.hpp"
#include "../../network/tablemodelptr.hpp"
class TableWidget;
@ -39,6 +40,7 @@ protected:
const ObjectPtr& object() const { return m_object; }
virtual void setTableModel(const TableModelPtr& tableModel);
virtual void tableSelectionChanged() {}
virtual void tableDoubleClicked(const QModelIndex& /*index*/) {}

Datei anzeigen

@ -0,0 +1,102 @@
/**
* client/src/widget/objectlist/zoneblocklistwidget.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "zoneblocklistwidget.hpp"
#include <QToolBar>
#include <QAction>
#include <traintastic/locale/locale.hpp>
#include "../../theme/theme.hpp"
#include "../../network/object.hpp"
#include "../../network/connection.hpp"
#include "../../network/tablemodel.hpp"
#include "../../board/blockhighlight.hpp"
#include "../../widget/tablewidget.hpp"
#include "../../mainwindow.hpp"
ZoneBlockListWidget::ZoneBlockListWidget(const ObjectPtr& object, QWidget* parent)
: ObjectListWidget(object, parent)
{
m_tableWidget->setFetchAll(true);
toolbar()->addSeparator();
m_actionHighlight = toolbar()->addAction(Theme::getIcon("highlight_zone"), Locale::tr("list.zone:highlight_zone"),
[this]()
{
updateHighlight();
});
m_actionHighlight->setCheckable(true);
}
ZoneBlockListWidget::~ZoneBlockListWidget()
{
if(m_actionHighlight->isChecked())
{
m_actionHighlight->setChecked(false);
updateHighlight();
}
}
void ZoneBlockListWidget::setTableModel(const TableModelPtr& tableModel)
{
ObjectListWidget::setTableModel(tableModel);
connect(tableModel.get(), &TableModel::modelReset, this,
[this]()
{
if(m_actionHighlight->isChecked())
{
updateHighlight();
}
});
}
void ZoneBlockListWidget::updateHighlight()
{
const auto highlight = m_actionHighlight->isChecked() ? m_tableWidget->getObjectIds() : QStringList();
if(!highlight.isEmpty() && m_highlightColor == Color::None)
{
m_highlightColor = MainWindow::instance->blockHighlight().colorPool.aquire();
}
for(const auto& blockId : m_highlight)
{
if(!highlight.contains(blockId))
{
MainWindow::instance->blockHighlight().remove(blockId, m_highlightColor);
}
}
for(const auto& blockId : highlight)
{
if(!m_highlight.contains(blockId))
{
MainWindow::instance->blockHighlight().add(blockId, m_highlightColor);
}
}
m_highlight = highlight;
if(m_highlight.isEmpty() && m_highlightColor != Color::None)
{
MainWindow::instance->blockHighlight().colorPool.release(m_highlightColor);
m_highlightColor = Color::None;
}
}

Datei anzeigen

@ -0,0 +1,46 @@
/**
* client/src/widget/objectlist/zoneblocklistwidget.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_ZONEBLOCKLISTWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_ZONEBLOCKLISTWIDGET_HPP
#include "objectlistwidget.hpp"
#include <traintastic/enum/color.hpp>
class ZoneBlockListWidget : public ObjectListWidget
{
private:
Color m_highlightColor = Color::None;
QStringList m_highlight;
QAction* m_actionHighlight;
void updateHighlight();
protected:
void setTableModel(const TableModelPtr& tableModel) override;
public:
explicit ZoneBlockListWidget(const ObjectPtr& object, QWidget* parent = nullptr);
~ZoneBlockListWidget() override;
};
#endif

Datei anzeigen

@ -54,6 +54,21 @@ QString TableWidget::getRowObjectId(int row) const
return m_model ? m_model->getRowObjectId(row) : "";
}
QStringList TableWidget::getObjectIds() const
{
assert(m_fetchAll);
QStringList ids;
if(m_model)
{
ids.reserve(m_model->rowCount());
for(int row = 0; row < m_model->rowCount(); ++row)
{
ids.append(m_model->getRowObjectId(row));
}
}
return ids;
}
void TableWidget::setTableModel(const TableModelPtr& model)
{
Q_ASSERT(!m_model);
@ -88,11 +103,34 @@ void TableWidget::setTableModel(const TableModelPtr& model)
updateRegion();
}
void TableWidget::setFetchAll(bool value)
{
if(m_fetchAll != value)
{
m_fetchAll = value;
if(m_model)
{
updateRegion();
}
}
}
void TableWidget::updateRegion()
{
assert(m_model);
const int columnCount = m_model->columnCount();
const int rowCount = m_model->rowCount();
if(columnCount == 0 || rowCount == 0)
return;
if(m_fetchAll)
{
m_model->setRegion(0, columnCount - 1, 0, rowCount - 1);
return;
}
const QRect r = viewport()->rect();
const QModelIndex topLeft = indexAt(r.topLeft());

Datei anzeigen

@ -34,6 +34,7 @@ class TableWidget : public QTableView
TableModelPtr m_model;
int m_selectedRow = -1;
QPoint m_dragStartPosition;
bool m_fetchAll = false;
void mouseMoveEvent(QMouseEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
@ -46,8 +47,10 @@ class TableWidget : public QTableView
~TableWidget() override;
QString getRowObjectId(int row) const;
QStringList getObjectIds() const;
void setTableModel(const TableModelPtr& model);
void setFetchAll(bool value);
signals:
void rowDragged(int row);

Datei anzeigen

@ -202,6 +202,10 @@
"TRAIN_LIST": {
"type": "constant"
},
"TRAIN_ZONE_STATUS": {
"type": "constant",
"since": "0.3"
},
"WORLD": {
"type": "constant",
"since": "0.1"
@ -212,5 +216,9 @@
"NX_BUTTON_RAIL_TILE": {
"type": "constant",
"since": "0.3"
},
"ZONE": {
"type": "constant",
"since": "0.3"
}
}

Datei anzeigen

@ -1,4 +1,6 @@
{
"active_train": {},
"mute": {},
"no_smoke": {},
"trains": {}
}

Datei anzeigen

@ -0,0 +1 @@
{}

Datei anzeigen

@ -6,9 +6,12 @@
"powered": {},
"active": {},
"mode": {},
"blocks": {},
"mute": {},
"no_smoke": {},
"has_throttle": {},
"throttle_name": {},
"blocks": {},
"zones": {},
"on_block_assigned": {
"parameters": [
{
@ -72,5 +75,71 @@
}
],
"since": "0.3"
},
"on_zone_assigned": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
],
"since": "0.3"
},
"on_zone_entering": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
],
"since": "0.3"
},
"on_zone_entered": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
],
"since": "0.3"
},
"on_zone_leaving": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
],
"since": "0.3"
},
"on_zone_left": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
],
"since": "0.3"
},
"on_zone_removed": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
],
"since": "0.3"
}
}

Datei anzeigen

@ -0,0 +1,11 @@
{
"state": {
"since": "0.3"
},
"train": {
"since": "0.3"
},
"zone": {
"since": "0.3"
}
}

Datei anzeigen

@ -46,5 +46,6 @@
}
],
"since": "0.1"
}
}
},
"zones": {}
}

Datei anzeigen

@ -0,0 +1,66 @@
{
"name": {},
"mute": {},
"no_smoke": {},
"trains": {},
"on_train_assigned": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
]
},
"on_train_entering": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
]
},
"on_train_entered": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
]
},
"on_train_leaving": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
]
},
"on_train_left": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
]
},
"on_train_removed": {
"parameters": [
{
"name": "train"
},
{
"name": "zone"
}
]
}
}

Datei anzeigen

@ -2031,6 +2031,194 @@
"term": "object.turnoutrailtile.set_position:return_values",
"definition": "`true` if the position is changed, `false` if position value is invalid or turnout is locked e.g. due to a reserved path."
},
{
"term": "object.zone:title",
"definition": "Zone"
},
{
"term": "object.zone:description",
"definition": "A group of blocks, this can for example be used to create a station or a staging area."
},
{
"term": "object.zone.name:description",
"definition": "Zone name."
},
{
"term": "object.zone.on_train_entering:description",
"definition": "Fired when a train reserves or enters the first block in the zone."
},
{
"term": "object.zone.on_train_entering.parameter.train:description",
"definition": "The {ref:object.train|train} that is entering the zone."
},
{
"term": "object.zone.on_train_entering.parameter.zone:description",
"definition": "The zone."
},
{
"term": "object.zone.on_train_assigned:description",
"definition": "Fired when a train is assigned to the first block in the zone."
},
{
"term": "object.zone.on_train_assigned.parameter.train:description",
"definition": "The {ref:object.train|train} that is assigned to a block in the zone."
},
{
"term": "object.zone.on_train_assigned.parameter.zone:description",
"definition": "The zone."
},
{
"term": "object.zone.on_train_leaving:description",
"definition": "Fired when a train reserves or enters a block that is not part of the zone."
},
{
"term": "object.zone.on_train_leaving.parameter.train:description",
"definition": "The {ref:object.train|train} that is leaving the zone."
},
{
"term": "object.zone.on_train_leaving.parameter.zone:description",
"definition": "The zone."
},
{
"term": "object.zone.on_train_entered:description",
"definition": "Fired when a train is in the zone, a train is in the zone when it only occupies blocks that are part of the zone."
},
{
"term": "object.zone.on_train_entered.parameter.train:description",
"definition": "The {ref:object.train|train} that is in the zone."
},
{
"term": "object.zone.on_train_entered.parameter.zone:description",
"definition": "The zone."
},
{
"term": "object.zone.on_train_left:description",
"definition": "Fired when a train is no longer in the zone, a train is not longer in the zone when it doesn't occupy any block that are part of the zone."
},
{
"term": "object.zone.on_train_left.parameter.train:description",
"definition": "The {ref:object.train|train} that has left the zone."
},
{
"term": "object.zone.on_train_left.parameter.zone:description",
"definition": "The zone."
},
{
"term": "object.zone.on_train_removed:description",
"definition": "Fired when a train is removed from the last block in the zone."
},
{
"term": "object.zone.on_train_removed.parameter.train:description",
"definition": "The {ref:object.train|train} that is removed from the zone."
},
{
"term": "object.zone.on_train_removed.parameter.zone:description",
"definition": "The zone."
},
{
"term": "object.trainzonestatus:title",
"definition": "Train zone status"
},
{
"term": "object.trainzonestatus.state:description",
"definition": "State of the train in the zone, a {ref:enum.zone_train_state} value."
},
{
"term": "object.trainzonestatus.train:description",
"definition": "The {ref:object.train|train}."
},
{
"term": "object.trainzonestatus.zone:description",
"definition": "The {ref:object.zone|zone}."
},
{
"term": "enum.zone_train_state:title",
"definition": "Zone train state"
},
{
"term": "object.train.on_zone_assigned:description",
"definition": "Fired when the train is assigned to the first block of a zone."
},
{
"term": "object.train.on_zone_assigned.parameter.train:description",
"definition": "The train."
},
{
"term": "object.train.on_zone_assigned.parameter.zone:description",
"definition": "The {ref:object.zone|zone} that the train is assigned to."
},
{
"term": "object.train.on_zone_entered:description",
"definition": "Fired when the train is in a zone, the train is in a zone when it only occupies blocks that are part of a zone."
},
{
"term": "object.train.on_zone_entered.parameter.train:description",
"definition": "The train."
},
{
"term": "object.train.on_zone_entered.parameter.zone:description",
"definition": "The {ref:object.zone|zone} that the train has entered."
},
{
"term": "object.train.on_zone_entering:description",
"definition": "Fired when the train reserves or enters the first block of a zone it currently isn't in."
},
{
"term": "object.train.on_zone_entering.parameter.train:description",
"definition": "The train."
},
{
"term": "object.train.on_zone_entering.parameter.zone:description",
"definition": "The {ref:object.zone|zone} that the train is entering."
},
{
"term": "object.train.on_zone_leaving:description",
"definition": "Fired when the train reserves or enters a block that is not part of a zone it currently is in."
},
{
"term": "object.train.on_zone_leaving.parameter.train:description",
"definition": "The train."
},
{
"term": "object.train.on_zone_leaving.parameter.zone:description",
"definition": "The {ref:object.zone|zone} that the train is leaving."
},
{
"term": "object.train.on_zone_left:description",
"definition": "Fired when the train is no longer in a zone, the train is not longer in a zone when it doesn't occupy any block that is part of a zone."
},
{
"term": "object.train.on_zone_left.parameter.train:description",
"definition": "The train."
},
{
"term": "object.train.on_zone_left.parameter.zone:description",
"definition": "The {ref:object.zone|zone} that the train left."
},
{
"term": "object.train.on_zone_removed:description",
"definition": "Fired when the train is removed from the last block of a zone."
},
{
"term": "object.train.on_zone_removed.parameter.train:description",
"definition": "The train."
},
{
"term": "object.train.on_zone_removed.parameter.zone:description",
"definition": "The {ref:object.zone|zone} that the train is removed from."
},
{
"term": "object.zone.mute:description",
"definition": "If `true`, all trains in the zone are muted."
},
{
"term": "object.zone.no_smoke:description",
"definition": "If `true`, smoke generaters are disabled of all trains in the zone."
},
{
"term": "object.zone.trains:description",
"definition": "List of {ref:object.trainzonestatus|train zone status} objects of all trains that are entering, entered or leaving the zone."
},
{
"term": "globals.pv:description",
"definition": "The {ref:pv|persistent variable} table."
@ -2078,5 +2266,41 @@
{
"term": "pv.checking:paragraph_1",
"definition": "To determine if a persistent variable has been set, use an `if` statement with `nil` checks. Variables in {ref:globals#pv|`pv`} that haven't been initialized or have been deleted will return `nil`. This pattern is useful for initializing default values or handling cases where the persistent variables are cleared."
},
{
"term": "enum.zone_train_state:description",
"definition": "Enum value represeting the state of a {ref:object.train|train} related to a {ref:object.zone|zone}. For long trains and/or short zones it is possible that a train is `LEAVING` before it fully `ENTERED` the zone, in that case `LEAVING` takes preference over `ENTERING`."
},
{
"term": "enum.zone_train_state.entered:description",
"definition": "The {ref:object.train|train} is in the {ref:object.zone|zone}, it does not occupy any {ref:object.blockrailtile|block} outside the zone."
},
{
"term": "enum.zone_train_state.entering:description",
"definition": "The front of the {ref:object.train|train} is in or has reserved at least one {ref:object.blockrailtile|block} of the {ref:object.zone|zone}."
},
{
"term": "enum.zone_train_state.leaving:description",
"definition": "The front of the {ref:object.train|train} is in or has reserved at least one {ref:object.blockrailtile|block} that is not of the {ref:object.zone|zone}."
},
{
"term": "enum.zone_train_state.unknown:description",
"definition": "The state is unknown, this value isn't used, it can be used for initialization purposes."
},
{
"term": "object.trainzonestatus:description",
"definition": "Object represeting the state of a train related to a zone."
},
{
"term": "object.train.mute:description",
"definition": "`true` if the train is currently muted by the {ref:object.world|world} mute or a muted {ref:object.zone|zone}, `false` otherwise."
},
{
"term": "object.train.no_smoke:description",
"definition": "`true` if the train's smoke generators are disabled by the {ref:object.world|world} no smoke or a non smoking {ref:object.zone|zone}, `false` otherwise."
},
{
"term": "object.train.zones:description",
"definition": "List of {ref:object.trainzonestatus|train zone status} objects of all zones that the train is entering, has entered and/or is leaving."
}
]

Datei anzeigen

@ -0,0 +1,40 @@
# Zones {#zones}
Zones allow you to apply common rules or restrictions to a group of blocks.
For example, you can create a **Staging zone** that mutes trains, or a **Shunting zone** with a speed limit.
- A block can belong to multiple zones.
- A zone can include any blocks, even if they are not physically adjacent.
## Zone list {#zone-list}
Open the zone list from the main menu: *Objects**Zones*.
From here you can manage all zones in your world:
- **Add a new zone** — Click the **+** button to create a new zone.
- **Remove a zone** — Select a zone and click the **–** button to delete it.
- **Edit a zone** — Double-click a zone in the list or use the **pencil** button to open its properties.
## Zone properties {#zone-properties}
Each zone has a set of properties that define its behavior.
### Identification
- **Name** — A short description of the zone, e.g. *Shunting Area* or *Staging Area*.
- **ID** — Unique identifier of the zone.
- Must be unique among all objects.
- Used for accessing the zone from the [Lua scripting engine](lua.md).
- Must start with a letter (`a–z`).
- May only contain lowercase letters (`a–z`), digits (`0–9`), and underscores (`_`).
### Block membership
- **Blocks** — List of blocks included in the zone.
Use the **+** and **–** buttons to add or remove blocks.
The **Highlight** button shows all blocks of the zone on opened boards.
### Behavioral settings
- **Mute** — When enabled, all trains in or entering the zone are muted (default: off).
- **No smoke** — When enabled, all smoke generators of trains in or entering the zone are disabled (default: off).
- **Speed limit** — Defines the maximum speed for trains in the zone.
This does **not** affect manually controlled trains.
By default, there is no speed limit.

Datei anzeigen

@ -60,6 +60,10 @@
}
]
},
{
"type": "chapter",
"markdown": "zone.md"
},
{
"type": "chapter",
"markdown": "clock.md"

Datei anzeigen

@ -40,7 +40,9 @@ if(BUILD_TESTING)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(Catch2 PRIVATE -Wno-restrict) # workaround GCC bug
endif()
add_executable(traintastic-server-test test/main.cpp)
add_executable(traintastic-server-test
test/zone.cpp
)
add_dependencies(traintastic-server-test traintastic-lang)
target_compile_definitions(traintastic-server-test PRIVATE -DTRAINTASTIC_TEST)
set_target_properties(traintastic-server-test PROPERTIES
@ -183,6 +185,8 @@ file(GLOB SOURCES
"src/utils/*.cpp"
"src/world/*.hpp"
"src/world/*.cpp"
"src/zone/*.hpp"
"src/zone/*.cpp"
"../shared/src/traintastic/locale/locale.cpp"
"../shared/src/traintastic/utils/standardpaths.cpp")

Datei anzeigen

@ -0,0 +1,39 @@
/**
* server/src/board/list/blockrailtilelist.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "blockrailtilelist.hpp"
#include "blockrailtilelisttablemodel.hpp"
BlockRailTileList::BlockRailTileList(Object& _parent, std::string_view parentPropertyName)
: ObjectList<BlockRailTile>(_parent, parentPropertyName)
{
}
TableModelPtr BlockRailTileList::getModel()
{
return std::make_shared<BlockRailTileListTableModel>(*this);
}
bool BlockRailTileList::isListedProperty(std::string_view name)
{
return BlockRailTileListTableModel::isListedProperty(name);
}

Datei anzeigen

@ -0,0 +1,42 @@
/**
* server/src/board/list/blockrailtilelist.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_BOARD_LIST_BLOCKRAILTILELIST_HPP
#define TRAINTASTIC_SERVER_BOARD_LIST_BLOCKRAILTILELIST_HPP
#include "../../core/objectlist.hpp"
#include "../tile/rail/blockrailtile.hpp"
class BlockRailTileList : public ObjectList<BlockRailTile>
{
CLASS_ID("list.block_rail_tile")
protected:
bool isListedProperty(std::string_view name) final;
public:
BlockRailTileList(Object& _parent, std::string_view parentPropertyName);
TableModelPtr getModel() final;
};
#endif

Datei anzeigen

@ -0,0 +1,75 @@
/**
* server/src/board/list/blockrailtilelisttablemodel.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "blockrailtilelisttablemodel.hpp"
#include "blockrailtilelist.hpp"
#include "../../utils/displayname.hpp"
constexpr uint32_t columnId = 0;
constexpr uint32_t columnName = 1;
bool BlockRailTileListTableModel::isListedProperty(std::string_view name)
{
return
name == "id" ||
name == "name";
}
BlockRailTileListTableModel::BlockRailTileListTableModel(ObjectList<BlockRailTile>& list) :
ObjectListTableModel<BlockRailTile>(list)
{
setColumnHeaders({
DisplayName::Object::id,
DisplayName::Object::name,
});
}
std::string BlockRailTileListTableModel::getText(uint32_t column, uint32_t row) const
{
if(row < rowCount())
{
const BlockRailTile& blockrailtile = getItem(row);
switch(column)
{
case columnId:
return blockrailtile.id;
case columnName:
return blockrailtile.name;
default:
assert(false);
break;
}
}
return "";
}
void BlockRailTileListTableModel::propertyChanged(BaseProperty& property, uint32_t row)
{
if(property.name() == "id")
changed(row, columnId);
else if(property.name() == "name")
changed(row, columnName);
}

Datei anzeigen

@ -0,0 +1,46 @@
/**
* server/src/board/list/blockrailtilelisttablemodel.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_BOARD_LIST_BLOCKRAILTILELISTTABLEMODEL_HPP
#define TRAINTASTIC_SERVER_BOARD_LIST_BLOCKRAILTILELISTTABLEMODEL_HPP
#include "../../core/objectlisttablemodel.hpp"
#include "../tile/rail/blockrailtile.hpp"
class BlockRailTileList;
class BlockRailTileListTableModel : public ObjectListTableModel<BlockRailTile>
{
protected:
void propertyChanged(BaseProperty& property, uint32_t row) final;
public:
CLASS_ID("table_model.blockrailtile_list")
static bool isListedProperty(std::string_view name);
BlockRailTileListTableModel(ObjectList<BlockRailTile>& list);
std::string getText(uint32_t column, uint32_t row) const final;
};
#endif

Datei anzeigen

@ -32,6 +32,10 @@
#include "../../../train/trainblockstatus.hpp"
#include "../../../train/traintracking.hpp"
#include "../../../utils/displayname.hpp"
#include "../../../zone/blockzonelist.hpp"
#include "../../../zone/zoneblocklist.hpp"
#include "../../list/blockrailtilelist.hpp"
#include "../../list/blockrailtilelisttablemodel.hpp"
#include "../../map/blockpath.hpp"
constexpr uint8_t toMask(BlockSide side)
@ -56,6 +60,7 @@ BlockRailTile::BlockRailTile(World& world, std::string_view _id) :
state{this, "state", BlockState::Unknown, PropertyFlags::ReadOnly | PropertyFlags::StoreState},
sensorStates{*this, "sensor_states", {}, PropertyFlags::ReadOnly | PropertyFlags::StoreState}
, trains{*this, "trains", {}, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, zones{this, "zones", {}, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
, assignTrain{*this, "assign_train",
[this](const std::shared_ptr<Train>& newTrain)
{
@ -113,8 +118,7 @@ BlockRailTile::BlockRailTile(World& world, std::string_view _id) :
}
}
newTrain->fireBlockAssigned(shared_ptr<BlockRailTile>());
fireEvent(onTrainAssigned, newTrain, self);
TrainTracking::assigned(newTrain, self);
}
}}
, removeTrain{*this, "remove_train",
@ -171,6 +175,7 @@ BlockRailTile::BlockRailTile(World& world, std::string_view _id) :
, onTrainRemoved{*this, "on_train_removed", EventFlags::Scriptable}
{
inputMap.setValueInternal(std::make_shared<BlockInputMap>(*this, inputMap.name()));
zones.setValueInternal(std::make_shared<BlockZoneList>(*this, zones.name()));
const bool editable = contains(m_world.state.value(), WorldState::Edit);
@ -191,6 +196,8 @@ BlockRailTile::BlockRailTile(World& world, std::string_view _id) :
Attributes::addObjectEditor(trains, false);
m_interfaceItems.add(trains);
m_interfaceItems.add(zones);
Attributes::addEnabled(assignTrain, true);
Attributes::addObjectEditor(assignTrain, false);
Attributes::addObjectList(assignTrain, world.trains);
@ -480,8 +487,7 @@ bool BlockRailTile::removeTrainInternal(const std::shared_ptr<TrainBlockStatus>
}
}
oldTrain->fireBlockRemoved(shared_ptr<BlockRailTile>());
fireEvent(onTrainRemoved, oldTrain, self);
TrainTracking::removed(oldTrain, shared_ptr<BlockRailTile>());
return true;
}
@ -541,6 +547,13 @@ void BlockRailTile::worldEvent(WorldState worldState, WorldEvent worldEvent)
Attributes::setEnabled(name, editable);
}
void BlockRailTile::addToWorld()
{
RailTile::addToWorld();
m_world.blockRailTiles->addObject(shared_ptr<BlockRailTile>());
}
void BlockRailTile::loaded()
{
RailTile::loaded();
@ -555,6 +568,11 @@ void BlockRailTile::destroying()
{
trains.back()->destroy();
}
while(!zones->empty())
{
zones->back()->blocks->remove(self);
}
m_world.blockRailTiles->removeObject(self);
RailTile::destroying();
}
@ -653,6 +671,11 @@ void BlockRailTile::updateHeightWidthMax()
Attributes::setMax<uint8_t>(width, !vertical ? TileData::widthMax : 1);
}
void BlockRailTile::fireTrainAssigned(const std::shared_ptr<Train>& train)
{
fireEvent(onTrainAssigned, train, shared_ptr<BlockRailTile>());
}
void BlockRailTile::fireTrainReserved(const std::shared_ptr<Train>& train, BlockTrainDirection trainDirection)
{
fireEvent(
@ -679,3 +702,8 @@ void BlockRailTile::fireTrainLeft(const std::shared_ptr<Train>& train, BlockTrai
shared_ptr<BlockRailTile>(),
trainDirection);
}
void BlockRailTile::fireTrainRemoved(const std::shared_ptr<Train>& train)
{
fireEvent(onTrainRemoved, train, shared_ptr<BlockRailTile>());
}

Datei anzeigen

@ -38,6 +38,7 @@ class Train;
class TrainBlockStatus;
class BlockInputMapItem;
class BlockPath;
class BlockZoneList;
class BlockRailTile : public RailTile
{
@ -60,12 +61,15 @@ class BlockRailTile : public RailTile
void updatePaths();
void updateHeightWidthMax();
void fireTrainAssigned(const std::shared_ptr<Train>& train);
void fireTrainReserved(const std::shared_ptr<Train>& train, BlockTrainDirection trainDirection);
void fireTrainEntered(const std::shared_ptr<Train>& train, BlockTrainDirection trainDirection);
void fireTrainLeft(const std::shared_ptr<Train>& train, BlockTrainDirection trainDirection);
void fireTrainRemoved(const std::shared_ptr<Train>& train);
protected:
void worldEvent(WorldState worldState, WorldEvent worldEvent) final;
void addToWorld() final;
void loaded() final;
void destroying() final;
void setRotate(TileRotate value) final;
@ -83,6 +87,7 @@ class BlockRailTile : public RailTile
Property<BlockState> state;
VectorProperty<SensorState> sensorStates;
ObjectVectorProperty<TrainBlockStatus> trains;
ObjectProperty<BlockZoneList> zones;
Method<void(std::shared_ptr<Train>)> assignTrain;
Method<void(std::shared_ptr<Train>)> removeTrain;
Method<void()> flipTrain;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2024 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -125,6 +125,8 @@ class ObjectList : public AbstractObjectList
inline const_iterator end() const noexcept { return m_items.end(); }
inline const std::shared_ptr<T>& front() const noexcept { return m_items.front(); }
inline std::shared_ptr<T>& front() noexcept { return m_items.front(); }
inline const std::shared_ptr<T>& back() const noexcept { return m_items.back(); }
inline std::shared_ptr<T>& back() noexcept { return m_items.back(); }
inline bool empty() const noexcept { return m_items.empty(); }
ObjectPtr getObject(uint32_t index) final

Datei anzeigen

@ -39,6 +39,11 @@ public:
SpeedLimitProperty(Object& object, std::string_view name, double value, SpeedUnit unit, PropertyFlags flags);
SpeedLimitProperty(Object& object, std::string_view name, double value, SpeedUnit unit, PropertyFlags flags, OnChanged onChanged);
inline bool hasLimit() const
{
return (m_value < noLimitValue);
}
};
#endif

Datei anzeigen

@ -115,9 +115,6 @@ Decoder::Decoder(World& world, std::string_view _id) :
{
functions.setValueInternal(std::make_shared<DecoderFunctions>(*this, functions.name()));
m_worldMute = contains(m_world.state.value(), WorldState::Mute);
m_worldNoSmoke = contains(m_world.state.value(), WorldState::NoSmoke);
Attributes::addDisplayName(name, DisplayName::Object::name);
Attributes::addEnabled(name, false);
m_interfaceItems.add(name);
@ -169,6 +166,8 @@ Decoder::Decoder(World& world, std::string_view _id) :
m_interfaceItems.add(notes);
updateEditable();
updateMute();
updateNoSmoke();
}
void Decoder::addToWorld()
@ -258,15 +257,15 @@ bool Decoder::getFunctionValue(const std::shared_ptr<const DecoderFunction>& fun
assert(this == &function->decoder());
// Apply mute/noSmoke world states:
if(m_worldMute)
// Apply mute/noSmoke:
if(m_mute)
{
if(function->function == DecoderFunctionFunction::Mute)
return true;
if(function->function == DecoderFunctionFunction::Sound && !getFunction(DecoderFunctionFunction::Mute))
return false;
}
if(m_worldNoSmoke)
if(m_noSmoke)
{
if(function->function == DecoderFunctionFunction::Smoke)
return false;
@ -307,6 +306,54 @@ void Decoder::release(Throttle& driver)
assert(false);
}
void Decoder::updateMute()
{
bool value = contains(m_world.state, WorldState::Mute);
if(!value && vehicle)
{
value |= vehicle->mute;
}
if(value != m_mute)
{
m_mute = value;
if(auto muteFn = getFunction(DecoderFunctionFunction::Mute))
{
if(muteFn->value != m_mute)
{
changed(DecoderChangeFlags::FunctionValue, muteFn->number);
}
}
else if(auto soundFn = getFunction(DecoderFunctionFunction::Sound))
{
if(soundFn->value == m_mute)
{
changed(DecoderChangeFlags::FunctionValue, soundFn->number);
}
}
}
}
void Decoder::updateNoSmoke()
{
bool value = contains(m_world.state, WorldState::NoSmoke);
if(!value && vehicle)
{
value |= vehicle->noSmoke;
}
if(value != m_noSmoke)
{
m_noSmoke = value;
if(auto smokeFn = getFunction(DecoderFunctionFunction::Smoke))
{
if(smokeFn->value == m_noSmoke)
{
changed(DecoderChangeFlags::FunctionValue, smokeFn->number);
}
}
}
}
void Decoder::destroying()
{
if(m_driver) // release driver throttle
@ -325,34 +372,20 @@ void Decoder::worldEvent(WorldState state, WorldEvent event)
IdObject::worldEvent(state, event);
updateEditable(contains(state, WorldState::Edit));
// Handle mute/noSmoke world states:
m_worldMute = contains(state, WorldState::Mute);
m_worldNoSmoke = contains(state, WorldState::NoSmoke);
if(event == WorldEvent::Mute || event == WorldEvent::Unmute)
switch(event)
{
bool hasMute = false;
case WorldEvent::Mute:
case WorldEvent::Unmute:
updateMute();
break;
for(const auto& f : *functions)
if(f->function == DecoderFunctionFunction::Mute)
{
if(!f->value)
changed(DecoderChangeFlags::FunctionValue, f->number);
hasMute = true;
}
case WorldEvent::NoSmoke:
case WorldEvent::Smoke:
updateNoSmoke();
break;
if(!hasMute)
{
for(const auto& f : *functions)
if(f->function == DecoderFunctionFunction::Sound && f->value)
changed(DecoderChangeFlags::FunctionValue, f->number);
}
}
else if(event == WorldEvent::NoSmoke || event == WorldEvent::Smoke)
{
for(const auto& f : *functions)
if(f->function == DecoderFunctionFunction::Smoke && f->value)
changed(DecoderChangeFlags::FunctionValue, f->number);
default:
break;
}
}

Datei anzeigen

@ -45,8 +45,8 @@ class Decoder : public IdObject
friend class DecoderFunction;
private:
bool m_worldMute;
bool m_worldNoSmoke;
bool m_mute = false;
bool m_noSmoke = false;
std::shared_ptr<Throttle> m_driver;
protected:
@ -131,6 +131,9 @@ class Decoder : public IdObject
bool acquire(Throttle& driver, bool steal = false);
void release(Throttle& driver);
void updateMute();
void updateNoSmoke();
};
#endif

Datei anzeigen

@ -104,9 +104,12 @@
#include "../train/train.hpp"
#include "../train/trainlist.hpp"
#include "../train/trainzonestatus.hpp"
#include "../world/world.hpp"
#include "../zone/zone.hpp"
namespace Lua {
static const char* metaTableName = "class";
@ -228,8 +231,11 @@ void Class::registerValues(lua_State* L)
registerValue<Train>(L, "TRAIN");
registerValue<TrainList>(L, "TRAIN_LIST");
registerValue<TrainZoneStatus>(L, "TRAIN_ZONE_STATUS");
registerValue<World>(L, "WORLD");
registerValue<Zone>(L, "ZONE");
}
void Class::push(lua_State* L, std::string_view classId)

Datei anzeigen

@ -42,6 +42,7 @@
#include "../../src/enum/signalaspect.hpp"
#include <traintastic/enum/worldevent.hpp>
#include "../../src/enum/worldscale.hpp"
#include <traintastic/enum/zonetrainstate.hpp>
#define LUA_ENUMS \
BlockTrainDirection, \
@ -60,7 +61,8 @@
TurnoutPosition, \
SignalAspect, \
WorldEvent, \
WorldScale
WorldScale, \
ZoneTrainState
namespace Lua {

Datei anzeigen

@ -38,6 +38,7 @@
#include "../throttle/throttle.hpp"
#include "../utils/almostzero.hpp"
#include "../utils/displayname.hpp"
#include "../zone/zone.hpp"
CREATE_IMPL(Train)
@ -78,6 +79,7 @@ Train::Train(World& world, std::string_view _id) :
isStopped{this, "is_stopped", true, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
speed{*this, "speed", 0, SpeedUnit::KiloMeterPerHour, PropertyFlags::ReadOnly | PropertyFlags::NoStore},
speedMax{*this, "speed_max", 0, SpeedUnit::KiloMeterPerHour, PropertyFlags::ReadWrite | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
speedLimit{*this, "speed_limit", SpeedLimitProperty::noLimitValue, SpeedUnit::KiloMeterPerHour, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
throttleSpeed{*this, "throttle_speed", 0, SpeedUnit::KiloMeterPerHour, PropertyFlags::ReadWrite | PropertyFlags::StoreState,
[this](double value, SpeedUnit unit)
{
@ -148,17 +150,26 @@ Train::Train(World& world, std::string_view _id) :
m_throttle->release();
}
},
std::bind(&Train::setTrainActive, this, std::placeholders::_1)},
mode{this, "mode", TrainMode::ManualUnprotected, PropertyFlags::ReadWrite | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly},
hasThrottle{this, "has_throttle", false, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
throttleName{this, "throttle_name", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
blocks{*this, "blocks", {}, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly},
notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
std::bind(&Train::setTrainActive, this, std::placeholders::_1)}
, mode{this, "mode", TrainMode::ManualUnprotected, PropertyFlags::ReadWrite | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, mute{this, "mute", false, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, noSmoke{this, "no_smoke", false, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, hasThrottle{this, "has_throttle", false, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, throttleName{this, "throttle_name", "", PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, blocks{*this, "blocks", {}, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, zones{*this, "zones", {}, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, notes{this, "notes", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
, onBlockAssigned{*this, "on_block_assigned", EventFlags::Scriptable}
, onBlockReserved{*this, "on_block_reserved", EventFlags::Scriptable}
, onBlockEntered{*this, "on_block_entered", EventFlags::Scriptable}
, onBlockLeft{*this, "on_block_left", EventFlags::Scriptable}
, onBlockRemoved{*this, "on_block_removed", EventFlags::Scriptable}
, onZoneAssigned{*this, "on_zone_assigned", EventFlags::Scriptable}
, onZoneEntering{*this, "on_zone_entering", EventFlags::Scriptable}
, onZoneEntered{*this, "on_zone_entered", EventFlags::Scriptable}
, onZoneLeaving{*this, "on_zone_leaving", EventFlags::Scriptable}
, onZoneLeft{*this, "on_zone_left", EventFlags::Scriptable}
, onZoneRemoved{*this, "on_zone_removed", EventFlags::Scriptable}
{
vehicles.setValueInternal(std::make_shared<TrainVehicleList>(*this, vehicles.name()));
@ -180,6 +191,10 @@ Train::Train(World& world, std::string_view _id) :
Attributes::addMinMax(speed, 0., 0., SpeedUnit::KiloMeterPerHour);
m_interfaceItems.add(speed);
m_interfaceItems.add(speedMax);
Attributes::addObjectEditor(speedLimit, false);
m_interfaceItems.add(speedLimit);
Attributes::addMinMax(throttleSpeed, 0., 0., SpeedUnit::KiloMeterPerHour);
Attributes::addEnabled(throttleSpeed, false);
Attributes::addObjectEditor(throttleSpeed, false);
@ -204,6 +219,12 @@ Train::Train(World& world, std::string_view _id) :
Attributes::addValues(mode, trainModeValues);
m_interfaceItems.add(mode);
Attributes::addObjectEditor(mute, false);
m_interfaceItems.add(mute);
Attributes::addObjectEditor(noSmoke, false);
m_interfaceItems.add(noSmoke);
Attributes::addObjectEditor(hasThrottle, false);
m_interfaceItems.add(hasThrottle);
@ -213,6 +234,9 @@ Train::Train(World& world, std::string_view _id) :
Attributes::addObjectEditor(blocks, false);
m_interfaceItems.add(blocks);
Attributes::addObjectEditor(zones, false);
m_interfaceItems.add(zones);
Attributes::addObjectEditor(powered, false);
m_interfaceItems.add(powered);
Attributes::addDisplayName(notes, DisplayName::Object::notes);
@ -223,8 +247,90 @@ Train::Train(World& world, std::string_view _id) :
m_interfaceItems.add(onBlockEntered);
m_interfaceItems.add(onBlockLeft);
m_interfaceItems.add(onBlockRemoved);
m_interfaceItems.add(onZoneAssigned);
m_interfaceItems.add(onZoneEntering);
m_interfaceItems.add(onZoneEntered);
m_interfaceItems.add(onZoneLeaving);
m_interfaceItems.add(onZoneLeft);
m_interfaceItems.add(onZoneRemoved);
updateEnabled();
updateMute();
updateNoSmoke();
}
void Train::updateMute()
{
bool value = contains(m_world.state, WorldState::Mute);
if(!value)
{
for(const auto& zoneStatus : zones)
{
if(zoneStatus->zone->mute)
{
value = true;
break;
}
}
}
if(value != mute)
{
mute.setValueInternal(value);
if(active)
{
for(const auto& vehicle : *vehicles)
{
vehicle->updateMute();
}
}
}
}
void Train::updateNoSmoke()
{
bool value = contains(m_world.state, WorldState::NoSmoke);
if(!value)
{
for(const auto& zoneStatus : zones)
{
if(zoneStatus->zone->noSmoke)
{
value = true;
break;
}
}
}
if(value != noSmoke)
{
noSmoke.setValueInternal(value);
if(active)
{
for(const auto& vehicle : *vehicles)
{
vehicle->updateNoSmoke();
}
}
}
}
void Train::updateSpeedLimit()
{
double value = SpeedLimitProperty::noLimitValue;
SpeedUnit unit = speedLimit.unit();
for(const auto& zoneStatus : zones)
{
const auto& zoneSpeedLimit = zoneStatus->zone->speedLimit;
if(zoneSpeedLimit.getValue(unit) < value)
{
unit = zoneSpeedLimit.unit();
value = zoneSpeedLimit.getValue(unit);
}
}
if(value != speedLimit.getValue(unit))
{
speedLimit.setValueInternal(value, unit);
//! \todo Apply to train
}
}
void Train::addToWorld()
@ -258,13 +364,34 @@ void Train::loaded()
}
vehiclesChanged();
updateMute();
updateNoSmoke();
}
void Train::worldEvent(WorldState state, WorldEvent event)
{
IdObject::worldEvent(state, event);
updateEnabled();
switch(event)
{
case WorldEvent::EditEnabled:
case WorldEvent::EditDisabled:
updateEnabled();
break;
case WorldEvent::Mute:
case WorldEvent::Unmute:
updateMute();
break;
case WorldEvent::NoSmoke:
case WorldEvent::Smoke:
updateNoSmoke();
break;
default:
break;
}
}
void Train::setSpeed(const double kmph)
@ -438,7 +565,7 @@ bool Train::setTrainActive(bool val)
//Register this train as activeTrain
for(const auto& vehicle : *vehicles)
{
vehicle->activeTrain.setValueInternal(self);
vehicle->setActiveTrain(self);
}
//Sync Emergency Stop state
@ -456,7 +583,7 @@ bool Train::setTrainActive(bool val)
for(const auto& vehicle : *vehicles)
{
assert(vehicle->activeTrain.value() == self);
vehicle->activeTrain.setValueInternal(nullptr);
vehicle->setActiveTrain(nullptr);
}
}
@ -605,3 +732,33 @@ void Train::fireBlockRemoved(const std::shared_ptr<BlockRailTile>& block)
shared_ptr<Train>(),
block);
}
void Train::fireZoneAssigned(const std::shared_ptr<Zone>& zone)
{
fireEvent(onZoneAssigned, shared_ptr<Train>(), zone);
}
void Train::fireZoneEntering(const std::shared_ptr<Zone>& zone)
{
fireEvent(onZoneEntering, shared_ptr<Train>(), zone);
}
void Train::fireZoneEntered(const std::shared_ptr<Zone>& zone)
{
fireEvent(onZoneEntered, shared_ptr<Train>(), zone);
}
void Train::fireZoneLeaving(const std::shared_ptr<Zone>& zone)
{
fireEvent(onZoneLeaving, shared_ptr<Train>(), zone);
}
void Train::fireZoneLeft(const std::shared_ptr<Zone>& zone)
{
fireEvent(onZoneLeft, shared_ptr<Train>(), zone);
}
void Train::fireZoneRemoved(const std::shared_ptr<Zone>& zone)
{
fireEvent(onZoneRemoved, shared_ptr<Train>(), zone);
}

Datei anzeigen

@ -38,8 +38,10 @@
class TrainVehicleList;
class TrainBlockStatus;
class TrainZoneStatus;
class BlockRailTile;
class PoweredRailVehicle;
class Zone;
class Throttle;
class Train : public IdObject
@ -76,6 +78,13 @@ class Train : public IdObject
void fireBlockEntered(const std::shared_ptr<BlockRailTile>& block, BlockTrainDirection trainDirection);
void fireBlockLeft(const std::shared_ptr<BlockRailTile>& block, BlockTrainDirection trainDirection);
void fireZoneAssigned(const std::shared_ptr<Zone>& zone);
void fireZoneEntering(const std::shared_ptr<Zone>& zone);
void fireZoneEntered(const std::shared_ptr<Zone>& zone);
void fireZoneLeaving(const std::shared_ptr<Zone>& zone);
void fireZoneLeft(const std::shared_ptr<Zone>& zone);
void fireZoneRemoved(const std::shared_ptr<Zone>& zone);
protected:
void addToWorld() override;
void destroying() override;
@ -93,6 +102,7 @@ class Train : public IdObject
Property<bool> isStopped;
SpeedProperty speed;
SpeedProperty speedMax;
SpeedProperty speedLimit;
SpeedProperty throttleSpeed;
Method<void()> stop;
Property<bool> emergencyStop;
@ -102,6 +112,8 @@ class Train : public IdObject
Property<bool> powered;
Property<bool> active;
Property<TrainMode> mode;
Property<bool> mute;
Property<bool> noSmoke;
Property<bool> hasThrottle;
Property<std::string> throttleName;
@ -109,15 +121,26 @@ class Train : public IdObject
//! Index 0 is the block where the head of the train is.
//! If the train changes direction this list will be reversed.
ObjectVectorProperty<TrainBlockStatus> blocks;
ObjectVectorProperty<TrainZoneStatus> zones;
Property<std::string> notes;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<BlockRailTile>&> onBlockAssigned;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<BlockRailTile>&, BlockTrainDirection> onBlockReserved;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<BlockRailTile>&, BlockTrainDirection> onBlockEntered;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<BlockRailTile>&, BlockTrainDirection> onBlockLeft;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<BlockRailTile>&> onBlockRemoved;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onZoneAssigned;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onZoneEntering;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onZoneEntered;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onZoneLeaving;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onZoneLeft;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onZoneRemoved;
Train(World& world, std::string_view _id);
void updateMute();
void updateNoSmoke();
void updateSpeedLimit();
std::error_code acquire(Throttle& throttle, bool steal = false);
std::error_code release(Throttle& throttle);
std::error_code setSpeed(Throttle& throttle, double value);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
* Copyright (C) 2024-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -26,19 +26,41 @@
#include "../board/tile/rail/blockrailtile.hpp"
#include "../board/map/blockpath.hpp"
#include "../core/objectproperty.tpp"
#include "../zone/blockzonelist.hpp"
#include "../world/world.hpp"
void TrainTracking::assigned(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block)
{
assert(train);
assert(block);
checkZoneAssigned(train, block);
train->fireBlockAssigned(block);
block->fireTrainAssigned(train);
}
void TrainTracking::reserve(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block, BlockTrainDirection direction)
{
assert(train);
assert(block);
checkZoneLeaving(train, block);
block->trains.appendInternal(TrainBlockStatus::create(*block, *train, direction));
block->updateTrainMethodEnabled();
checkZoneEntering(train, block);
train->fireBlockReserved(block, direction);
block->fireTrainReserved(train, direction);
}
void TrainTracking::enter(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block, BlockTrainDirection direction)
{
assert(train);
assert(block);
checkZoneLeaving(train, block);
auto status = TrainBlockStatus::create(*block, *train, direction);
block->trains.appendInternal(status);
@ -47,14 +69,17 @@ void TrainTracking::enter(const std::shared_ptr<Train>& train, const std::shared
enter(status);
}
void TrainTracking::enter(const std::shared_ptr<TrainBlockStatus>& status)
void TrainTracking::enter(const std::shared_ptr<TrainBlockStatus>& blockStatus)
{
assert(status);
const auto& train = status->train.value();
const auto& block = status->block.value();
const auto direction = status->direction.value();
assert(blockStatus);
const auto& train = blockStatus->train.value();
const auto& block = blockStatus->block.value();
const auto direction = blockStatus->direction.value();
status->train->blocks.insertInternal(0, status); // head of train
blockStatus->train->blocks.insertInternal(0, blockStatus); // head of train
checkZoneEntering(train, block);
checkZoneEntered(train, block);
train->fireBlockEntered(block, direction);
block->fireTrainEntered(train, direction);
@ -77,18 +102,22 @@ void TrainTracking::enter(const std::shared_ptr<TrainBlockStatus>& status)
}
}
void TrainTracking::left(std::shared_ptr<TrainBlockStatus> status)
void TrainTracking::left(std::shared_ptr<TrainBlockStatus> blockStatus)
{
const auto& train = status->train.value();
const auto& block = status->block.value();
const auto direction = status->direction.value();
assert(blockStatus);
const auto& train = blockStatus->train.value();
const auto& block = blockStatus->block.value();
const auto direction = blockStatus->direction.value();
train->blocks.removeInternal(status);
block->trains.removeInternal(status);
train->blocks.removeInternal(blockStatus);
block->trains.removeInternal(blockStatus);
block->updateTrainMethodEnabled();
block->updateState();
checkZoneEntered(train, block);
checkZoneLeft(train, block);
train->fireBlockLeft(block, direction);
block->fireTrainLeft(train, direction);
@ -107,10 +136,201 @@ void TrainTracking::left(std::shared_ptr<TrainBlockStatus> status)
pathB->delayedRelease(block->world().pathReleaseDelay);
}
status->destroy();
blockStatus->destroy();
#ifndef NDEBUG
std::weak_ptr<TrainBlockStatus> statusWeak = status;
status.reset();
assert(statusWeak.expired());
std::weak_ptr<TrainBlockStatus> blockStatusWeak = blockStatus;
blockStatus.reset();
assert(blockStatusWeak.expired());
#endif
}
void TrainTracking::removed(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block)
{
assert(train);
assert(block);
checkZoneRemoved(train, block);
train->fireBlockRemoved(block);
block->fireTrainRemoved(train);
}
void TrainTracking::checkZoneAssigned(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block)
{
assert(train);
assert(block);
for(auto& zone : *block->zones)
{
auto zoneStatus = zone->getTrainZoneStatus(train);
if(zoneStatus)
{
continue; // train already known
}
zoneStatus = TrainZoneStatus::create(*zone, *train, ZoneTrainState::Entered);
zone->trains.appendInternal(zoneStatus);
train->zones.appendInternal(zoneStatus);
trainEnteredOrLeftZone(*train, *zone);
train->fireZoneAssigned(zone);
zone->fireTrainAssigned(train);
}
}
void TrainTracking::checkZoneEntering(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block)
{
assert(train);
assert(block);
for(auto& zone : *block->zones)
{
auto zoneStatus = zone->getTrainZoneStatus(train);
if(zoneStatus && (zoneStatus->state == ZoneTrainState::Entering || zoneStatus->state == ZoneTrainState::Entered))
{
continue; // train already in or entering zone
}
if(!zoneStatus)
{
zoneStatus = TrainZoneStatus::create(*zone, *train, ZoneTrainState::Entering);
zone->trains.appendInternal(zoneStatus);
train->zones.appendInternal(zoneStatus);
}
else
{
assert(zoneStatus->state.value() == ZoneTrainState::Leaving);
zoneStatus->state.setValueInternal(ZoneTrainState::Entering);
}
zone->fireTrainEntering(train);
train->fireZoneEntering(zone);
}
}
void TrainTracking::checkZoneEntered(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block)
{
assert(train);
assert(block);
for(auto& zone : *block->zones)
{
auto zoneStatus = zone->getTrainZoneStatus(train);
if(zoneStatus && zoneStatus->state == ZoneTrainState::Entered)
{
continue; // train already in zone
}
if(!zoneStatus)
{
zoneStatus = TrainZoneStatus::create(*zone, *train, ZoneTrainState::Entered);
zone->trains.appendInternal(zoneStatus);
train->zones.appendInternal(zoneStatus);
}
else
{
assert(zoneStatus->state == ZoneTrainState::Entering || zoneStatus->state.value() == ZoneTrainState::Leaving);
zoneStatus->state.setValueInternal(ZoneTrainState::Entered);
}
trainEnteredOrLeftZone(*train, *zone);
zone->fireTrainEntered(train);
train->fireZoneEntered(zone);
}
}
void TrainTracking::checkZoneLeaving(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block)
{
assert(train);
assert(block);
for(const auto& zoneStatus : *train->zones)
{
const auto& zone = zoneStatus->zone.value();
if(!block->zones->containsObject(zone))
{
zoneStatus->state.setValueInternal(ZoneTrainState::Leaving);
zone->fireTrainLeaving(train);
train->fireZoneLeaving(zone);
}
}
}
void TrainTracking::checkZoneLeft(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block)
{
assert(train);
assert(block);
for(const auto& zone : *block->zones)
{
if(removeTrainIfNotInZone(train, zone))
{
trainEnteredOrLeftZone(*train, *zone);
zone->fireTrainLeft(train);
train->fireZoneLeft(zone);
}
}
}
void TrainTracking::checkZoneRemoved(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block)
{
assert(train);
assert(block);
for(auto& zone : *block->zones)
{
if(removeTrainIfNotInZone(train, zone))
{
trainEnteredOrLeftZone(*train, *zone);
train->fireZoneRemoved(zone);
zone->fireTrainRemoved(train);
}
}
}
bool TrainTracking::removeTrainIfNotInZone(const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
assert(train);
assert(zone);
if(std::find_if(train->blocks.begin(), train->blocks.end(),
[&zone](const auto& trainBlockStatus)
{
return trainBlockStatus->block->zones->containsObject(zone);
}) == train->blocks.end())
{
auto zoneStatus = zone->getTrainZoneStatus(train);
zone->trains.removeInternal(zoneStatus);
train->zones.removeInternal(zoneStatus);
zoneStatus->destroy();
#ifndef NDEBUG
std::weak_ptr<TrainZoneStatus> zoneStatusWeak = zoneStatus;
zoneStatus.reset();
assert(zoneStatusWeak.expired());
#endif
return true; // removed
}
return false;
}
void TrainTracking::trainEnteredOrLeftZone(Train& train, Zone& zone)
{
// Re-evaluate zone limitations:
if(zone.mute)
{
train.updateMute();
}
if(zone.noSmoke)
{
train.updateNoSmoke();
}
if(zone.speedLimit.hasLimit())
{
train.updateSpeedLimit();
}
}

Datei anzeigen

@ -29,6 +29,7 @@
class Train;
class BlockRailTile;
class TrainBlockStatus;
class Zone;
class TrainTracking final
{
@ -37,11 +38,24 @@ private:
TrainTracking(const TrainTracking&) = delete;
TrainTracking& operator=(const TrainTracking&) = delete;
static void checkZoneAssigned(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block);
static void checkZoneEntering(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block);
static void checkZoneEntered(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block);
static void checkZoneLeaving(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block);
static void checkZoneLeft(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block);
static void checkZoneRemoved(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block);
static bool removeTrainIfNotInZone(const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone);
static void trainEnteredOrLeftZone(Train& train, Zone& zone);
public:
static void assigned(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block);
static void reserve(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block, BlockTrainDirection direction);
static void enter(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block, BlockTrainDirection direction); // enter unreserved block
static void enter(const std::shared_ptr<TrainBlockStatus>& status); // enter reserved block
static void left(std::shared_ptr<TrainBlockStatus> status);
static void enter(const std::shared_ptr<TrainBlockStatus>& blockStatus); // enter reserved block
static void left(std::shared_ptr<TrainBlockStatus> blockStatus);
static void removed(const std::shared_ptr<Train>& train, const std::shared_ptr<BlockRailTile>& block);
};
#endif

Datei anzeigen

@ -47,13 +47,13 @@ TrainVehicleList::TrainVehicleList(Train& train_, std::string_view parentPropert
vehicle->trains.appendInternal(parent().shared_ptr<Train>());
if(train().active)
vehicle->activeTrain.setValueInternal(parent().shared_ptr<Train>());
vehicle->setActiveTrain(parent().shared_ptr<Train>());
}}
, remove{*this, "remove",
[this](const std::shared_ptr<RailVehicle>& vehicle)
{
if(vehicle->activeTrain.value() == train().shared_ptr<Train>())
vehicle->activeTrain.setValueInternal(nullptr);
vehicle->setActiveTrain(nullptr);
vehicle->trains.removeInternal(parent().shared_ptr<Train>());
removeObject(vehicle);

Datei anzeigen

@ -0,0 +1,64 @@
/**
* server/src/train/trainzonestatus.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "trainzonestatus.hpp"
#include "train.hpp"
#include "../core/objectproperty.tpp"
#include "../core/attributes.hpp"
#include "../world/world.hpp"
#include "../zone/zone.hpp"
std::shared_ptr<TrainZoneStatus> TrainZoneStatus::create(Zone& zone_, Train& train_, ZoneTrainState state_, std::string_view id)
{
World& world = zone_.world();
auto p = std::make_shared<TrainZoneStatus>(id.empty() ? world.getUniqueId(TrainZoneStatus::classId) : std::string{id});
p->zone.setValueInternal(zone_.shared_ptr<Zone>());
p->train.setValueInternal(train_.shared_ptr<Train>());
p->state.setValueInternal(state_);
TrainZoneStatus::addToWorld(world, *p);
return p;
}
TrainZoneStatus::TrainZoneStatus(std::string id)
: StateObject(std::move(id))
, zone{this, "zone", nullptr, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, train{this, "train", nullptr, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, state{this, "state", ZoneTrainState::Unknown, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
{
m_interfaceItems.add(zone);
m_interfaceItems.add(train);
Attributes::addValues(state, zoneTrainStateValues);
m_interfaceItems.add(state);
}
void TrainZoneStatus::destroying()
{
auto self = shared_ptr<TrainZoneStatus>();
if(zone)
zone->trains.removeInternal(self);
if(train)
train->zones.removeInternal(self);
removeFromWorld(zone->world(), *this);
StateObject::destroying();
}

Datei anzeigen

@ -0,0 +1,52 @@
/**
* server/src/train/trainzonestatus.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_TRAIN_TRAINZONESTATUS_HPP
#define TRAINTASTIC_SERVER_TRAIN_TRAINZONESTATUS_HPP
#include "../core/stateobject.hpp"
#include "../core/property.hpp"
#include "../core/objectproperty.hpp"
#include <traintastic/enum/zonetrainstate.hpp>
class Zone;
class Train;
//! \brief Represents the state of a train within a zone
class TrainZoneStatus final : public StateObject
{
CLASS_ID("train_zone_status");
protected:
void destroying() final;
public:
static std::shared_ptr<TrainZoneStatus> create(Zone& zone_, Train& train_, ZoneTrainState state_, std::string_view id = {});
ObjectProperty<Zone> zone;
ObjectProperty<Train> train;
Property<ZoneTrainState> state;
TrainZoneStatus(std::string id);
};
#endif

Datei anzeigen

@ -55,8 +55,10 @@ RailVehicle::RailVehicle(World& world, std::string_view _id) :
lob{*this, "lob", 0, LengthUnit::MilliMeter, PropertyFlags::ReadWrite | PropertyFlags::Store},
speedMax{*this, "speed_max", 0, SpeedUnit::KiloMeterPerHour, PropertyFlags::ReadWrite | PropertyFlags::Store},
weight{*this, "weight", 0, WeightUnit::Ton, PropertyFlags::ReadWrite | PropertyFlags::Store, [this](double /*value*/, WeightUnit /*unit*/){ updateTotalWeight(); }},
totalWeight{*this, "total_weight", 0, WeightUnit::Ton, PropertyFlags::ReadOnly | PropertyFlags::NoStore},
activeTrain{this, "active_train", nullptr, PropertyFlags::ReadOnly | PropertyFlags::ScriptReadOnly | PropertyFlags::StoreState}
totalWeight{*this, "total_weight", 0, WeightUnit::Ton, PropertyFlags::ReadOnly | PropertyFlags::NoStore}
, mute{this, "mute", false, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, noSmoke{this, "no_smoke", false, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly}
, activeTrain{this, "active_train", nullptr, PropertyFlags::ReadOnly | PropertyFlags::ScriptReadOnly | PropertyFlags::StoreState}
, trains{*this, "trains", {}, PropertyFlags::ReadOnly | PropertyFlags::ScriptReadOnly | PropertyFlags::NoStore}
{
const bool editable = contains(m_world.state.value(), WorldState::Edit);
@ -82,6 +84,12 @@ RailVehicle::RailVehicle(World& world, std::string_view _id) :
Attributes::addDisplayName(totalWeight, DisplayName::Vehicle::Rail::totalWeight);
m_interfaceItems.insertBefore(totalWeight, notes);
Attributes::addObjectEditor(mute, false);
m_interfaceItems.add(mute);
Attributes::addObjectEditor(noSmoke, false);
m_interfaceItems.add(noSmoke);
Attributes::addDisplayName(activeTrain, DisplayName::Vehicle::Rail::train); //TODO: "Active"
Attributes::addEnabled(activeTrain, true);
m_interfaceItems.insertBefore(activeTrain, notes);
@ -90,6 +98,47 @@ RailVehicle::RailVehicle(World& world, std::string_view _id) :
m_interfaceItems.insertBefore(trains, notes);
}
void RailVehicle::setActiveTrain(const std::shared_ptr<Train>& train)
{
activeTrain.setValueInternal(train);
updateMute();
updateNoSmoke();
}
void RailVehicle::updateMute()
{
bool value = contains(m_world.state, WorldState::NoSmoke);
if(!value && activeTrain)
{
value |= activeTrain->mute;
}
if(value != mute)
{
mute.setValueInternal(value);
if(decoder)
{
decoder->updateMute();
}
}
}
void RailVehicle::updateNoSmoke()
{
bool value = contains(m_world.state, WorldState::NoSmoke);
if(!value && activeTrain)
{
value |= activeTrain->noSmoke;
}
if(value != noSmoke)
{
noSmoke.setValueInternal(value);
if(decoder)
{
decoder->updateNoSmoke();
}
}
}
void RailVehicle::addToWorld()
{
Vehicle::addToWorld();
@ -125,12 +174,31 @@ void RailVehicle::worldEvent(WorldState state, WorldEvent event)
{
Vehicle::worldEvent(state, event);
const bool editable = contains(state, WorldState::Edit);
switch(event)
{
case WorldEvent::EditEnabled:
case WorldEvent::EditDisabled:
{
const bool editable = contains(state, WorldState::Edit);
Attributes::setEnabled(decoder, editable);
Attributes::setEnabled(lob, editable);
Attributes::setEnabled(speedMax, editable);
Attributes::setEnabled(weight, editable);
break;
}
case WorldEvent::Mute:
case WorldEvent::Unmute:
updateMute();
break;
Attributes::setEnabled(decoder, editable);
Attributes::setEnabled(lob, editable);
Attributes::setEnabled(speedMax, editable);
Attributes::setEnabled(weight, editable);
case WorldEvent::NoSmoke:
case WorldEvent::Smoke:
updateNoSmoke();
break;
default:
break;
}
}
double RailVehicle::calcTotalWeight(WeightUnit unit) const

Datei anzeigen

@ -52,9 +52,16 @@ class RailVehicle : public Vehicle
SpeedProperty speedMax;
WeightProperty weight;
WeightProperty totalWeight;
Property<bool> mute;
Property<bool> noSmoke;
ObjectProperty<Train> activeTrain;
ObjectVectorProperty<Train> trains;
void setActiveTrain(const std::shared_ptr<Train>& train);
void updateMute();
void updateNoSmoke();
};
#endif

Datei anzeigen

@ -59,10 +59,14 @@
#include "../board/board.hpp"
#include "../board/boardlist.hpp"
#include "../board/list/blockrailtilelist.hpp"
#include "../board/list/linkrailtilelist.hpp"
#include "../board/nx/nxmanager.hpp"
#include "../board/tile/rail/nxbuttonrailtile.hpp"
#include "../zone/zone.hpp"
#include "../zone/zonelist.hpp"
#include "../throttle/list/throttlelist.hpp"
#include "../train/train.hpp"
#include "../train/trainlist.hpp"
@ -126,12 +130,14 @@ void World::init(World& world)
world.outputs.setValueInternal(std::make_shared<OutputList>(world, world.outputs.name(), outputListColumns));
world.identifications.setValueInternal(std::make_shared<IdentificationList>(world, world.outputs.name(), identificationListColumns));
world.boards.setValueInternal(std::make_shared<BoardList>(world, world.boards.name()));
world.zones.setValueInternal(std::make_shared<ZoneList>(world, world.zones.name()));
world.clock.setValueInternal(std::make_shared<Clock>(world, world.clock.name()));
world.throttles.setValueInternal(std::make_shared<ThrottleList>(world, world.throttles.name(), throttleListColumns));
world.trains.setValueInternal(std::make_shared<TrainList>(world, world.trains.name()));
world.railVehicles.setValueInternal(std::make_shared<RailVehicleList>(world, world.railVehicles.name()));
world.luaScripts.setValueInternal(std::make_shared<Lua::ScriptList>(world, world.luaScripts.name()));
world.blockRailTiles.setValueInternal(std::make_shared<BlockRailTileList>(world, world.blockRailTiles.name()));
world.linkRailTiles.setValueInternal(std::make_shared<LinkRailTileList>(world, world.linkRailTiles.name()));
world.nxManager.setValueInternal(std::make_shared<NXManager>(world, world.nxManager.name()));
@ -174,11 +180,13 @@ World::World(Private /*unused*/) :
outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
identifications{this, "identifications", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
boards{this, "boards", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
zones{this, "zones", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
clock{this, "clock", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::Store | PropertyFlags::ScriptReadOnly},
throttles{this, "throttles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
trains{this, "trains", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
railVehicles{this, "rail_vehicles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore | PropertyFlags::ScriptReadOnly},
luaScripts{this, "lua_scripts", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
blockRailTiles{this, "block_rail_tiles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
linkRailTiles{this, "link_rail_tiles", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
nxManager{this, "nx_manager", nullptr, PropertyFlags::ReadOnly | PropertyFlags::SubObject | PropertyFlags::NoStore},
statuses(*this, "statuses", {}, PropertyFlags::ReadOnly | PropertyFlags::Store),
@ -377,6 +385,10 @@ World::World(Private /*unused*/) :
m_interfaceItems.add(throttles);
Attributes::addObjectEditor(boards, false);
m_interfaceItems.add(boards);
Attributes::addObjectEditor(zones, false);
m_interfaceItems.add(zones);
Attributes::addObjectEditor(clock, false);
m_interfaceItems.add(clock);
Attributes::addObjectEditor(trains, false);
@ -386,6 +398,9 @@ World::World(Private /*unused*/) :
Attributes::addObjectEditor(luaScripts, false);
m_interfaceItems.add(luaScripts);
Attributes::addObjectEditor(blockRailTiles, false);
m_interfaceItems.add(blockRailTiles);
Attributes::addObjectEditor(linkRailTiles, false);
m_interfaceItems.add(linkRailTiles);
Attributes::addObjectEditor(nxManager, false);
@ -443,6 +458,7 @@ World::~World()
deleteAll(*inputs);
deleteAll(*identifications);
deleteAll(*boards);
deleteAll(*zones);
deleteAll(*throttles);
deleteAll(*trains);
deleteAll(*railVehicles);

Datei anzeigen

@ -50,6 +50,8 @@ class InputList;
class OutputList;
class IdentificationList;
class BoardList;
class ZoneList;
class BlockRailTileList;
class LinkRailTileList;
class NXManager;
class Clock;
@ -122,12 +124,14 @@ class World : public Object
ObjectProperty<OutputList> outputs;
ObjectProperty<IdentificationList> identifications;
ObjectProperty<BoardList> boards;
ObjectProperty<ZoneList> zones;
ObjectProperty<Clock> clock;
ObjectProperty<ThrottleList> throttles;
ObjectProperty<TrainList> trains;
ObjectProperty<RailVehicleList> railVehicles;
ObjectProperty<Lua::ScriptList> luaScripts;
ObjectProperty<BlockRailTileList> blockRailTiles;
ObjectProperty<LinkRailTileList> linkRailTiles;
ObjectProperty<NXManager> nxManager;

Datei anzeigen

@ -45,6 +45,7 @@
#include "../train/train.hpp"
#include "../train/trainblockstatus.hpp"
#include "../lua/script.hpp"
#include "../zone/zone.hpp"
using nlohmann::json;
@ -276,8 +277,20 @@ void WorldLoader::createObject(ObjectData& objectData)
}
}
}
else if(classId == TrainZoneStatus::classId)
{
if(auto zone = std::dynamic_pointer_cast<Zone>(getObject(objectData.json["zone"].get<std::string_view>()))) /*[[likely]]*/
{
auto train = std::dynamic_pointer_cast<Train>(getObject(objectData.json["train"].get<std::string_view>()));
objectData.object = TrainZoneStatus::create(*zone, *train, to<ZoneTrainState>(objectData.json["state"]), id);
}
}
else if(classId == Lua::Script::classId)
objectData.object = Lua::Script::create(*m_world, id);
else if(classId == Zone::classId)
{
objectData.object = Zone::create(*m_world, id);
}
if(!objectData.object)
throw LogMessageException(LogMessage::C1012_UNKNOWN_CLASS_X_CANT_RECREATE_OBJECT_X, classId, id);

Datei anzeigen

@ -0,0 +1,86 @@
/**
* server/src/zone/blockzonelist.cpp
*
* This file is part of the zonetastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "blockzonelist.hpp"
#include "zoneblocklist.hpp"
#include "zonelisttablemodel.hpp"
#include "zone.hpp"
#include "../world/getworld.hpp"
#include "../world/world.hpp"
#include "../core/attributes.hpp"
#include "../core/method.tpp"
#include "../core/objectproperty.tpp"
#include "../utils/displayname.hpp"
BlockZoneList::BlockZoneList(BlockRailTile& block_, std::string_view parentPropertyName)
: ObjectList<Zone>(block_, parentPropertyName)
, add{*this, "add",
[this](const std::shared_ptr<Zone>& zone)
{
if(!containsObject(zone))
{
addObject(zone);
zone->blocks->add(parent().shared_ptr<BlockRailTile>());
}
}}
, remove{*this, "remove",
[this](const std::shared_ptr<Zone>& zone)
{
if(containsObject(zone))
{
removeObject(zone);
zone->blocks->remove(parent().shared_ptr<BlockRailTile>());
}
}}
{
const auto& world = getWorld(parent());
Attributes::addDisplayName(add, DisplayName::List::add);
Attributes::addEnabled(add, false);
Attributes::addObjectList(add, world.zones);
m_interfaceItems.add(add);
Attributes::addDisplayName(remove, DisplayName::List::remove);
Attributes::addEnabled(remove, false);
m_interfaceItems.add(remove);
}
TableModelPtr BlockZoneList::getModel()
{
return std::make_shared<ZoneListTableModel>(*this);
}
void BlockZoneList::worldEvent(WorldState worldState, WorldEvent worldEvent)
{
ObjectList<Zone>::worldEvent(worldState, worldEvent);
Attributes::setEnabled({add, remove}, contains(worldState, WorldState::Edit) && !contains(worldState, WorldState::Run));
}
bool BlockZoneList::isListedProperty(std::string_view name)
{
return ZoneListTableModel::isListedProperty(name);
}
BlockRailTile& BlockZoneList::block()
{
return static_cast<BlockRailTile&>(parent());
}

Datei anzeigen

@ -0,0 +1,53 @@
/**
* server/src/zone/blockzonelist.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_ZONE_BLOCKZONELIST_HPP
#define TRAINTASTIC_SERVER_ZONE_BLOCKZONELIST_HPP
#include "../core/method.hpp"
#include "../core/objectlist.hpp"
#include "zone.hpp"
class BlockRailTile;
class Zone;
class BlockZoneList : public ObjectList<Zone>
{
CLASS_ID("list.block_zone")
private:
inline BlockRailTile& block();
protected:
void worldEvent(WorldState worldState, WorldEvent worldEvent) override;
bool isListedProperty(std::string_view name) final;
public:
Method<void(const std::shared_ptr<Zone>&)> add;
Method<void(const std::shared_ptr<Zone>&)> remove;
BlockZoneList(BlockRailTile& block_, std::string_view parentPropertyName);
TableModelPtr getModel() final;
};
#endif

170
server/src/zone/zone.cpp Normale Datei
Datei anzeigen

@ -0,0 +1,170 @@
/**
* server/src/zone/zone.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "zone.hpp"
#include "zoneblocklist.hpp"
#include "zonelist.hpp"
#include "zonelisttablemodel.hpp"
#include "blockzonelist.hpp"
#include "../core/attributes.hpp"
#include "../core/method.tpp"
#include "../core/objectproperty.tpp"
#include "../core/objectvectorproperty.tpp"
#include "../train/train.hpp"
#include "../utils/displayname.hpp"
#include "../world/world.hpp"
CREATE_IMPL(Zone)
Zone::Zone(World& world, std::string_view id_)
: IdObject(world, id_)
, name{this, "name", id, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly}
, mute{this, "mute", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly,
[this](bool /*value*/)
{
for(auto& status : trains)
{
status->train->updateMute();
}
}}
, noSmoke{this, "no_smoke", false, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly,
[this](bool /*value*/)
{
for(auto& status : trains)
{
status->train->updateNoSmoke();
}
}}
, speedLimit{*this, "speed_limit", SpeedLimitProperty::noLimitValue, SpeedUnit::KiloMeterPerHour, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::ScriptReadOnly,
[this](double /*value*/, SpeedUnit /*unit*/)
{
for(auto& status : trains)
{
status->train->updateSpeedLimit();
}
}}
, blocks{this, "blocks", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
, trains{*this, "trains", {}, PropertyFlags::ReadOnly | PropertyFlags::StoreState | PropertyFlags::ScriptReadOnly}
, onTrainAssigned{*this, "on_train_assigned", EventFlags::Scriptable}
, onTrainEntering{*this, "on_train_entering", EventFlags::Scriptable}
, onTrainEntered{*this, "on_train_entered", EventFlags::Scriptable}
, onTrainLeaving{*this, "on_train_leaving", EventFlags::Scriptable}
, onTrainLeft{*this, "on_train_left", EventFlags::Scriptable}
, onTrainRemoved{*this, "on_train_removed", EventFlags::Scriptable}
{
blocks.setValueInternal(std::make_shared<ZoneBlockList>(*this, blocks.name()));
const bool editable = contains(world.state.value(), WorldState::Edit);
Attributes::addDisplayName(name, DisplayName::Object::name);
Attributes::addEnabled(name, editable);
m_interfaceItems.add(name);
Attributes::addEnabled(mute, editable);
m_interfaceItems.add(mute);
Attributes::addEnabled(noSmoke, editable);
m_interfaceItems.add(noSmoke);
Attributes::addEnabled(speedLimit, editable);
m_interfaceItems.add(speedLimit);
m_interfaceItems.add(blocks);
Attributes::addObjectEditor(trains, false);
m_interfaceItems.add(trains);
m_interfaceItems.add(onTrainAssigned);
m_interfaceItems.add(onTrainEntering);
m_interfaceItems.add(onTrainEntered);
m_interfaceItems.add(onTrainLeaving);
m_interfaceItems.add(onTrainLeft);
m_interfaceItems.add(onTrainRemoved);
}
std::shared_ptr<TrainZoneStatus> Zone::getTrainZoneStatus(const std::shared_ptr<Train>& train)
{
auto it = std::find_if(trains.begin(), trains.end(),
[&train](const auto& status)
{
return status->train.value() == train;
});
return (it != trains.end()) ? *it : std::shared_ptr<TrainZoneStatus>{};
}
void Zone::worldEvent(WorldState worldState, WorldEvent worldEvent)
{
IdObject::worldEvent(worldState, worldEvent);
const bool editable = contains(worldState, WorldState::Edit);
Attributes::setEnabled({name, mute, noSmoke, speedLimit}, editable);
}
void Zone::addToWorld()
{
IdObject::addToWorld();
m_world.zones->addObject(shared_ptr<Zone>());
}
void Zone::destroying()
{
auto self = shared_ptr<Zone>();
while(!blocks->empty())
{
blocks->back()->zones->remove(self);
}
m_world.zones->removeObject(self);
IdObject::destroying();
}
void Zone::fireTrainAssigned(const std::shared_ptr<Train>& train)
{
fireEvent(onTrainAssigned, train, shared_ptr<Zone>());
}
void Zone::fireTrainEntering(const std::shared_ptr<Train>& train)
{
fireEvent(onTrainEntering, train, shared_ptr<Zone>());
}
void Zone::fireTrainEntered(const std::shared_ptr<Train>& train)
{
fireEvent(onTrainEntered, train, shared_ptr<Zone>());
}
void Zone::fireTrainLeaving(const std::shared_ptr<Train>& train)
{
fireEvent(onTrainLeaving, train, shared_ptr<Zone>());
}
void Zone::fireTrainLeft(const std::shared_ptr<Train>& train)
{
fireEvent(onTrainLeft, train, shared_ptr<Zone>());
}
void Zone::fireTrainRemoved(const std::shared_ptr<Train>& train)
{
fireEvent(onTrainRemoved, train, shared_ptr<Zone>());
}

75
server/src/zone/zone.hpp Normale Datei
Datei anzeigen

@ -0,0 +1,75 @@
/**
* server/src/zone/zone.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_ZONE_ZONE_HPP
#define TRAINTASTIC_SERVER_ZONE_ZONE_HPP
#include "../core/idobject.hpp"
#include "../core/event.hpp"
#include "../core/objectproperty.hpp"
#include "../core/objectvectorproperty.hpp"
#include "../core/speedlimitproperty.hpp"
#include "../train/trainzonestatus.hpp"
class ZoneBlockList;
class Train;
class Zone : public IdObject
{
friend class TrainTracking;
CLASS_ID("zone")
CREATE_DEF(Zone)
DEFAULT_ID("zone")
protected:
void worldEvent(WorldState worldState, WorldEvent worldEvent) override;
void addToWorld() override;
void destroying() override;
void fireTrainAssigned(const std::shared_ptr<Train>& train);
void fireTrainEntering(const std::shared_ptr<Train>& train);
void fireTrainEntered(const std::shared_ptr<Train>& train);
void fireTrainLeaving(const std::shared_ptr<Train>& train);
void fireTrainLeft(const std::shared_ptr<Train>& train);
void fireTrainRemoved(const std::shared_ptr<Train>& train);
public:
Zone(World& world, std::string_view id_);
std::shared_ptr<TrainZoneStatus> getTrainZoneStatus(const std::shared_ptr<Train>& train);
Property<std::string> name;
Property<bool> mute;
Property<bool> noSmoke;
SpeedLimitProperty speedLimit;
ObjectProperty<ZoneBlockList> blocks;
ObjectVectorProperty<TrainZoneStatus> trains;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onTrainAssigned;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onTrainEntering;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onTrainEntered;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onTrainLeaving;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onTrainLeft;
Event<const std::shared_ptr<Train>&, const std::shared_ptr<Zone>&> onTrainRemoved;
};
#endif

Datei anzeigen

@ -0,0 +1,85 @@
/**
* server/src/zone/zoneblocklist.cpp
*
* This file is part of the zonetastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "zoneblocklist.hpp"
#include "blockzonelist.hpp"
#include "../board/list/blockrailtilelisttablemodel.hpp"
#include "../world/getworld.hpp"
#include "../core/attributes.hpp"
#include "../core/method.tpp"
#include "../core/objectproperty.tpp"
#include "../utils/displayname.hpp"
ZoneBlockList::ZoneBlockList(Zone& zone_, std::string_view parentPropertyName)
: ObjectList<BlockRailTile>(zone_, parentPropertyName)
, add{*this, "add",
[this](const std::shared_ptr<BlockRailTile>& block)
{
if(!containsObject(block))
{
addObject(block);
block->zones->add(parent().shared_ptr<Zone>());
}
}}
, remove{*this, "remove",
[this](const std::shared_ptr<BlockRailTile>& block)
{
if(containsObject(block))
{
removeObject(block);
block->zones->remove(parent().shared_ptr<Zone>());
}
}}
{
const auto& world = getWorld(parent());
const bool editable = contains(world.state.value(), WorldState::Edit);
Attributes::addDisplayName(add, DisplayName::List::add);
Attributes::addEnabled(add, editable);
Attributes::addObjectList(add, world.blockRailTiles);
m_interfaceItems.add(add);
Attributes::addDisplayName(remove, DisplayName::List::remove);
Attributes::addEnabled(remove, editable);
m_interfaceItems.add(remove);
}
TableModelPtr ZoneBlockList::getModel()
{
return std::make_shared<BlockRailTileListTableModel>(*this);
}
void ZoneBlockList::worldEvent(WorldState worldState, WorldEvent worldEvent)
{
ObjectList<BlockRailTile>::worldEvent(worldState, worldEvent);
Attributes::setEnabled({add, remove}, contains(worldState, WorldState::Edit) && !contains(worldState, WorldState::Run));
}
bool ZoneBlockList::isListedProperty(std::string_view name)
{
return BlockRailTileListTableModel::isListedProperty(name);
}
Zone& ZoneBlockList::zone()
{
return static_cast<Zone&>(parent());
}

Datei anzeigen

@ -0,0 +1,53 @@
/**
* server/src/zone/zoneblocklist.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_ZONE_ZONEBLOCKLIST_HPP
#define TRAINTASTIC_SERVER_ZONE_ZONEBLOCKLIST_HPP
#include "../core/method.hpp"
#include "../core/objectlist.hpp"
#include "../board/tile/rail/blockrailtile.hpp"
class Zone;
class BlockRailTile;
class ZoneBlockList : public ObjectList<BlockRailTile>
{
private:
inline Zone& zone();
protected:
void worldEvent(WorldState worldState, WorldEvent worldEvent) override;
bool isListedProperty(std::string_view name) final;
public:
CLASS_ID("list.zone_block")
Method<void(const std::shared_ptr<BlockRailTile>&)> add;
Method<void(const std::shared_ptr<BlockRailTile>&)> remove;
ZoneBlockList(Zone& zone_, std::string_view parentPropertyName);
TableModelPtr getModel() final;
};
#endif

75
server/src/zone/zonelist.cpp Normale Datei
Datei anzeigen

@ -0,0 +1,75 @@
/**
* server/src/zone/zonelist.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "zone.hpp"
#include "zonelist.hpp"
#include "zonelisttablemodel.hpp"
#include "../world/world.hpp"
#include "../world/getworld.hpp"
#include "../core/attributes.hpp"
#include "../core/method.tpp"
#include "../utils/displayname.hpp"
ZoneList::ZoneList(Object& _parent, std::string_view parentPropertyName)
: ObjectList<Zone>(_parent, parentPropertyName)
, create{*this, "create",
[this]()
{
auto& world = getWorld(parent());
return Zone::create(world, world.getUniqueId(Zone::defaultId));
}}
, delete_{*this, "delete",
[this](const std::shared_ptr<Zone>& zone)
{
deleteMethodHandler(zone);
}}
{
const bool editable = contains(getWorld(parent()).state.value(), WorldState::Edit);
Attributes::addDisplayName(create, DisplayName::List::create);
Attributes::addEnabled(create, editable);
m_interfaceItems.add(create);
Attributes::addDisplayName(delete_, DisplayName::List::delete_);
Attributes::addEnabled(delete_, editable);
m_interfaceItems.add(delete_);
}
TableModelPtr ZoneList::getModel()
{
return std::make_shared<ZoneListTableModel>(*this);
}
void ZoneList::worldEvent(WorldState state, WorldEvent event)
{
ObjectList<Zone>::worldEvent(state, event);
const bool editable = contains(state, WorldState::Edit);
Attributes::setEnabled(create, editable);
Attributes::setEnabled(delete_, editable);
}
bool ZoneList::isListedProperty(std::string_view name)
{
return ZoneListTableModel::isListedProperty(name);
}

48
server/src/zone/zonelist.hpp Normale Datei
Datei anzeigen

@ -0,0 +1,48 @@
/**
* server/src/zone/zonelist.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_ZONE_ZONELIST_HPP
#define TRAINTASTIC_SERVER_ZONE_ZONELIST_HPP
#include "../core/objectlist.hpp"
#include "../core/method.hpp"
class Zone;
class ZoneList : public ObjectList<Zone>
{
CLASS_ID("list.zone")
protected:
void worldEvent(WorldState state, WorldEvent event) final;
bool isListedProperty(std::string_view name) final;
public:
Method<std::shared_ptr<Zone>()> create;
Method<void(const std::shared_ptr<Zone>&)> delete_;
ZoneList(Object& _parent, std::string_view parentPropertyName);
TableModelPtr getModel() final;
};
#endif

Datei anzeigen

@ -0,0 +1,77 @@
/**
* server/src/zone/zonelisttablemodel.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "zonelisttablemodel.hpp"
#include "zone.hpp"
#include "zonelist.hpp"
#include "../core/objectproperty.tpp"
#include "../utils/displayname.hpp"
constexpr uint32_t columnId = 0;
constexpr uint32_t columnName = 1;
bool ZoneListTableModel::isListedProperty(std::string_view name)
{
return
name == "id" ||
name == "name";
}
ZoneListTableModel::ZoneListTableModel(ObjectList<Zone>& list) :
ObjectListTableModel<Zone>(list)
{
setColumnHeaders({
DisplayName::Object::id,
DisplayName::Object::name,
});
}
std::string ZoneListTableModel::getText(uint32_t column, uint32_t row) const
{
if(row < rowCount())
{
const Zone& zone = getItem(row);
switch(column)
{
case columnId:
return zone.id;
case columnName:
return zone.name;
default:
assert(false);
break;
}
}
return "";
}
void ZoneListTableModel::propertyChanged(BaseProperty& property, uint32_t row)
{
if(property.name() == "id")
changed(row, columnId);
else if(property.name() == "name")
changed(row, columnName);
}

Datei anzeigen

@ -0,0 +1,46 @@
/**
* server/src/zone/zonelisttablemodel.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_ZONE_ZONELISTTABLEMODEL_HPP
#define TRAINTASTIC_SERVER_ZONE_ZONELISTTABLEMODEL_HPP
#include "../core/objectlisttablemodel.hpp"
class Zone;
class ZoneList;
class ZoneListTableModel : public ObjectListTableModel<Zone>
{
CLASS_ID("zone_list_table_model")
protected:
void propertyChanged(BaseProperty& property, uint32_t row) final;
public:
static bool isListedProperty(std::string_view name);
ZoneListTableModel(ObjectList<Zone>& list);
std::string getText(uint32_t column, uint32_t row) const final;
};
#endif

Datei anzeigen

@ -26,6 +26,7 @@
#include "../src/core/objectproperty.tpp"
#include "../src/board/board.hpp"
#include "../src/board/boardlist.hpp"
#include "../src/board/tile/rail/blockrailtile.hpp"
#include "../src/hardware/decoder/list/decoderlist.hpp"
#include "../src/hardware/interface/interfacelist.hpp"
#include "../src/hardware/input/input.hpp"
@ -36,6 +37,10 @@
#include "vehicle/rail/railvehicles.hpp"
#include "../src/train/trainlist.hpp"
#include "../src/train/train.hpp"
#include "../src/zone/zonelist.hpp"
#include "../src/zone/zone.hpp"
#include "../src/zone/zoneblocklist.hpp"
#include "../src/zone/blockzonelist.hpp"
TEST_CASE("Create world => destroy world", "[object-create-destroy]")
{
@ -443,3 +448,194 @@ TEST_CASE("Create world and train => destroy train", "[object-create-destroy]")
world.reset();
REQUIRE(worldWeak.expired());
}
TEST_CASE("Create world and zone => destroy world", "[object-create-destroy]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(zoneWeak.lock()->getClassId() == Zone::classId);
world.reset();
REQUIRE(zoneWeak.expired());
REQUIRE(worldWeak.expired());
}
TEST_CASE("Create world and zone => destroy zone", "[object-create-destroy]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(worldWeak.lock()->zones->length == 0);
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(worldWeak.lock()->zones->length == 1);
world->zones->delete_(zoneWeak.lock());
REQUIRE(zoneWeak.expired());
REQUIRE(worldWeak.lock()->zones->length == 0);
world.reset();
REQUIRE(worldWeak.expired());
}
TEST_CASE("Create world, board, block and zone => destroy world", "[object-create-destroy]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(worldWeak.lock()->boards->length == 0);
REQUIRE(worldWeak.lock()->zones->length == 0);
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(worldWeak.lock()->boards->length == 1);
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg0, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(worldWeak.lock()->zones->length == 1);
REQUIRE(blockWeak.lock()->zones->length == 0);
REQUIRE(zoneWeak.lock()->blocks->length == 0);
zoneWeak.lock()->blocks->add(blockWeak.lock());
REQUIRE(blockWeak.lock()->zones->length == 1);
REQUIRE(blockWeak.lock()->zones->front() == zoneWeak.lock());
REQUIRE(zoneWeak.lock()->blocks->length == 1);
REQUIRE(zoneWeak.lock()->blocks->front() == blockWeak.lock());
world.reset();
REQUIRE(blockWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(zoneWeak.expired());
REQUIRE(worldWeak.expired());
}
TEST_CASE("Create world, board, block and zone => destroy board", "[object-create-destroy]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(worldWeak.lock()->boards->length == 0);
REQUIRE(worldWeak.lock()->zones->length == 0);
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(worldWeak.lock()->boards->length == 1);
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg0, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(worldWeak.lock()->zones->length == 1);
REQUIRE(blockWeak.lock()->zones->length == 0);
REQUIRE(zoneWeak.lock()->blocks->length == 0);
zoneWeak.lock()->blocks->add(blockWeak.lock());
REQUIRE(blockWeak.lock()->zones->length == 1);
REQUIRE(blockWeak.lock()->zones->front() == zoneWeak.lock());
REQUIRE(zoneWeak.lock()->blocks->length == 1);
REQUIRE(zoneWeak.lock()->blocks->front() == blockWeak.lock());
world->boards->delete_(boardWeak.lock());
REQUIRE(blockWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(zoneWeak.lock()->blocks->length == 0);
REQUIRE_FALSE(worldWeak.expired());
world.reset();
REQUIRE(zoneWeak.expired());
REQUIRE(worldWeak.expired());
}
TEST_CASE("Create world, board, block and zone => destroy block", "[object-create-destroy]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(worldWeak.lock()->boards->length == 0);
REQUIRE(worldWeak.lock()->zones->length == 0);
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(worldWeak.lock()->boards->length == 1);
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg0, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(worldWeak.lock()->zones->length == 1);
REQUIRE(blockWeak.lock()->zones->length == 0);
REQUIRE(zoneWeak.lock()->blocks->length == 0);
zoneWeak.lock()->blocks->add(blockWeak.lock());
REQUIRE(blockWeak.lock()->zones->length == 1);
REQUIRE(blockWeak.lock()->zones->front() == zoneWeak.lock());
REQUIRE(zoneWeak.lock()->blocks->length == 1);
REQUIRE(zoneWeak.lock()->blocks->front() == blockWeak.lock());
REQUIRE(boardWeak.lock()->deleteTile(0, 0));
REQUIRE(blockWeak.expired());
REQUIRE_FALSE(boardWeak.expired());
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(zoneWeak.lock()->blocks->length == 0);
REQUIRE_FALSE(worldWeak.expired());
world.reset();
REQUIRE(boardWeak.expired());
REQUIRE(zoneWeak.expired());
REQUIRE(worldWeak.expired());
}
TEST_CASE("Create world, board, block and zone => destroy zone", "[object-create-destroy]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(worldWeak.lock()->boards->length == 0);
REQUIRE(worldWeak.lock()->zones->length == 0);
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(worldWeak.lock()->boards->length == 1);
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg0, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(worldWeak.lock()->zones->length == 1);
REQUIRE(blockWeak.lock()->zones->length == 0);
REQUIRE(zoneWeak.lock()->blocks->length == 0);
zoneWeak.lock()->blocks->add(blockWeak.lock());
REQUIRE(blockWeak.lock()->zones->length == 1);
REQUIRE(blockWeak.lock()->zones->front() == zoneWeak.lock());
REQUIRE(zoneWeak.lock()->blocks->length == 1);
REQUIRE(zoneWeak.lock()->blocks->front() == blockWeak.lock());
world->zones->delete_(zoneWeak.lock());
REQUIRE(zoneWeak.expired());
REQUIRE_FALSE(blockWeak.expired());
REQUIRE(blockWeak.lock()->zones->length == 0);
REQUIRE_FALSE(boardWeak.expired());
REQUIRE_FALSE(worldWeak.expired());
world.reset();
REQUIRE(blockWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(worldWeak.expired());
}

766
server/test/zone.cpp Normale Datei
Datei anzeigen

@ -0,0 +1,766 @@
/**
* This file is part of Traintastic,
* see <https://github.com/traintastic/traintastic>.
*
* Copyright (C) 2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_approx.hpp>
#include "../src/core/attributes.hpp"
#include "../src/core/method.tpp"
#include "../src/core/objectproperty.tpp"
#include "../src/board/board.hpp"
#include "../src/board/boardlist.hpp"
#include "../src/board/tile/rail/blockrailtile.hpp"
#include "../src/world/world.hpp"
#include "../src/vehicle/rail/railvehiclelist.hpp"
#include "../src/vehicle/rail/railvehiclelisttablemodel.hpp"
#include "../src/vehicle/rail/locomotive.hpp"
#include "../src/train/trainlist.hpp"
#include "../src/train/train.hpp"
#include "../src/train/trainvehiclelist.hpp"
#include "../src/hardware/decoder/decoder.hpp"
#include "../src/zone/blockzonelist.hpp"
#include "../src/zone/zonelist.hpp"
#include "../src/zone/zonelisttablemodel.hpp"
#include "../src/zone/zone.hpp"
#include "../src/zone/zoneblocklist.hpp"
TEST_CASE("Zone: Assign/remove train to/from muted, no smoke and speed limited zone", "[zone]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(world->railVehicles->length == 0);
std::weak_ptr<RailVehicle> locomotiveWeak = world->railVehicles->create(Locomotive::classId);
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(world->railVehicles->length == 1);
REQUIRE(world->trains->length == 0);
std::weak_ptr<Train> trainWeak = world->trains->create();
REQUIRE_FALSE(trainWeak.expired());
REQUIRE(world->trains->length == 1);
REQUIRE(trainWeak.lock()->vehicles->length == 0);
trainWeak.lock()->vehicles->add(locomotiveWeak.lock());
REQUIRE(trainWeak.lock()->vehicles->length == 1);
REQUIRE(world->boards->length == 0);
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(world->boards->length == 1);
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg90, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE_FALSE(zoneWeak.lock()->mute.value());
REQUIRE_FALSE(zoneWeak.lock()->noSmoke.value());
zoneWeak.lock()->mute = true;
zoneWeak.lock()->noSmoke = true;
zoneWeak.lock()->speedLimit.setValue(100.0);
REQUIRE(zoneWeak.lock()->mute);
REQUIRE(zoneWeak.lock()->noSmoke);
REQUIRE(zoneWeak.lock()->speedLimit.value() == Catch::Approx(100.0));
REQUIRE(zoneWeak.lock()->blocks->length == 0);
zoneWeak.lock()->blocks->add(blockWeak.lock());
REQUIRE(zoneWeak.lock()->blocks->length == 1);
REQUIRE(zoneWeak.lock()->trains.size() == 0);
// Assign train to block in muted, no smoke and speed limited zone:
REQUIRE_FALSE(trainWeak.lock()->active);
REQUIRE_FALSE(trainWeak.lock()->mute);
REQUIRE_FALSE(trainWeak.lock()->noSmoke);
REQUIRE(std::isinf(trainWeak.lock()->speedLimit.value()));
REQUIRE_FALSE(locomotiveWeak.lock()->mute);
REQUIRE_FALSE(locomotiveWeak.lock()->noSmoke);
blockWeak.lock()->assignTrain(trainWeak.lock());
REQUIRE(trainWeak.lock()->active);
REQUIRE(trainWeak.lock()->blocks.size() == 1);
REQUIRE(trainWeak.lock()->zones.size() == 1);
REQUIRE(blockWeak.lock()->trains.size() == 1);
REQUIRE(zoneWeak.lock()->trains.size() == 1);
REQUIRE(trainWeak.lock()->mute);
REQUIRE(trainWeak.lock()->noSmoke);
REQUIRE(trainWeak.lock()->speedLimit.value() == Catch::Approx(100.0));
REQUIRE(locomotiveWeak.lock()->mute);
REQUIRE(locomotiveWeak.lock()->noSmoke);
// Remove train from block in muted, no smoke and speed limited zone:
blockWeak.lock()->removeTrain(trainWeak.lock());
REQUIRE_FALSE(trainWeak.lock()->active);
REQUIRE(trainWeak.lock()->blocks.size() == 0);
REQUIRE(trainWeak.lock()->zones.size() == 0);
REQUIRE(blockWeak.lock()->trains.size() == 0);
REQUIRE(zoneWeak.lock()->trains.size() == 0);
REQUIRE_FALSE(trainWeak.lock()->mute);
REQUIRE_FALSE(trainWeak.lock()->noSmoke);
REQUIRE(std::isinf(trainWeak.lock()->speedLimit.value()));
REQUIRE_FALSE(locomotiveWeak.lock()->mute);
REQUIRE_FALSE(locomotiveWeak.lock()->noSmoke);
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(locomotiveWeak.expired());
REQUIRE(trainWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(blockWeak.expired());
REQUIRE(zoneWeak.expired());
}
TEST_CASE("Zone: Assign/remove events", "[zone]")
{
size_t trainZoneAssignedEventCount = 0;
size_t trainZoneEnteringEventCount = 0;
size_t trainZoneEnteredEventCount = 0;
size_t trainZoneLeavingEventCount = 0;
size_t trainZoneLeftEventCount = 0;
size_t trainZoneRemovedEventCount = 0;
size_t zoneTrainAssignedEventCount = 0;
size_t zoneTrainEnteringEventCount = 0;
size_t zoneTrainEnteredEventCount = 0;
size_t zoneTrainLeavingEventCount = 0;
size_t zoneTrainLeftEventCount = 0;
size_t zoneTrainRemovedEventCount = 0;
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(world->railVehicles->length == 0);
std::weak_ptr<RailVehicle> locomotiveWeak = world->railVehicles->create(Locomotive::classId);
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(world->railVehicles->length == 1);
REQUIRE(world->trains->length == 0);
std::weak_ptr<Train> trainWeak = world->trains->create();
REQUIRE_FALSE(trainWeak.expired());
REQUIRE(world->trains->length == 1);
REQUIRE(trainWeak.lock()->vehicles->length == 0);
trainWeak.lock()->vehicles->add(locomotiveWeak.lock());
REQUIRE(trainWeak.lock()->vehicles->length == 1);
REQUIRE(world->boards->length == 0);
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(world->boards->length == 1);
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg90, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(zoneWeak.lock()->blocks->length == 0);
zoneWeak.lock()->blocks->add(blockWeak.lock());
REQUIRE(zoneWeak.lock()->blocks->length == 1);
REQUIRE(zoneWeak.lock()->trains.size() == 0);
// Setup events:
trainWeak.lock()->onZoneAssigned.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
trainZoneAssignedEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
trainWeak.lock()->onZoneEntering.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
trainZoneEnteringEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
trainWeak.lock()->onZoneEntered.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
trainZoneEnteredEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
trainWeak.lock()->onZoneLeaving.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
trainZoneLeavingEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
trainWeak.lock()->onZoneLeft.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
trainZoneLeftEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
trainWeak.lock()->onZoneRemoved.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
trainZoneRemovedEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
zoneWeak.lock()->onTrainAssigned.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
zoneTrainAssignedEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
zoneWeak.lock()->onTrainEntering.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
zoneTrainEnteringEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
zoneWeak.lock()->onTrainEntered.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
zoneTrainEnteredEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
zoneWeak.lock()->onTrainLeaving.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
zoneTrainLeavingEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
zoneWeak.lock()->onTrainLeft.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
zoneTrainLeftEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
zoneWeak.lock()->onTrainRemoved.connect(
[&](const std::shared_ptr<Train>& train, const std::shared_ptr<Zone>& zone)
{
zoneTrainRemovedEventCount++;
REQUIRE(train == trainWeak.lock());
REQUIRE(zone == zoneWeak.lock());
});
REQUIRE(trainZoneAssignedEventCount == 0);
REQUIRE(trainZoneEnteringEventCount == 0);
REQUIRE(trainZoneEnteredEventCount == 0);
REQUIRE(trainZoneLeavingEventCount == 0);
REQUIRE(trainZoneLeftEventCount == 0);
REQUIRE(trainZoneRemovedEventCount == 0);
REQUIRE(zoneTrainAssignedEventCount == 0);
REQUIRE(zoneTrainEnteringEventCount == 0);
REQUIRE(zoneTrainEnteredEventCount == 0);
REQUIRE(zoneTrainLeavingEventCount == 0);
REQUIRE(zoneTrainLeftEventCount == 0);
REQUIRE(zoneTrainRemovedEventCount == 0);
blockWeak.lock()->assignTrain(trainWeak.lock());
REQUIRE(trainZoneAssignedEventCount == 1);
REQUIRE(trainZoneEnteringEventCount == 0);
REQUIRE(trainZoneEnteredEventCount == 0);
REQUIRE(trainZoneLeavingEventCount == 0);
REQUIRE(trainZoneLeftEventCount == 0);
REQUIRE(trainZoneRemovedEventCount == 0);
REQUIRE(zoneTrainAssignedEventCount == 1);
REQUIRE(zoneTrainEnteringEventCount == 0);
REQUIRE(zoneTrainEnteredEventCount == 0);
REQUIRE(zoneTrainLeavingEventCount == 0);
REQUIRE(zoneTrainLeftEventCount == 0);
REQUIRE(zoneTrainRemovedEventCount == 0);
blockWeak.lock()->removeTrain(trainWeak.lock());
REQUIRE(trainZoneAssignedEventCount == 1);
REQUIRE(trainZoneEnteringEventCount == 0);
REQUIRE(trainZoneEnteredEventCount == 0);
REQUIRE(trainZoneLeavingEventCount == 0);
REQUIRE(trainZoneLeftEventCount == 0);
REQUIRE(trainZoneRemovedEventCount == 1);
REQUIRE(zoneTrainAssignedEventCount == 1);
REQUIRE(zoneTrainEnteringEventCount == 0);
REQUIRE(zoneTrainEnteredEventCount == 0);
REQUIRE(zoneTrainLeavingEventCount == 0);
REQUIRE(zoneTrainLeftEventCount == 0);
REQUIRE(zoneTrainRemovedEventCount == 1);
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(locomotiveWeak.expired());
REQUIRE(trainWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(blockWeak.expired());
REQUIRE(zoneWeak.expired());
REQUIRE(trainZoneAssignedEventCount == 1);
REQUIRE(trainZoneEnteringEventCount == 0);
REQUIRE(trainZoneEnteredEventCount == 0);
REQUIRE(trainZoneLeavingEventCount == 0);
REQUIRE(trainZoneLeftEventCount == 0);
REQUIRE(trainZoneRemovedEventCount == 1);
REQUIRE(zoneTrainAssignedEventCount == 1);
REQUIRE(zoneTrainEnteringEventCount == 0);
REQUIRE(zoneTrainEnteredEventCount == 0);
REQUIRE(zoneTrainLeavingEventCount == 0);
REQUIRE(zoneTrainLeftEventCount == 0);
REQUIRE(zoneTrainRemovedEventCount == 1);
}
TEST_CASE("Zone: Toggle mute/noSmoke/speedLimit with train in zone", "[zone]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(world->railVehicles->length == 0);
std::weak_ptr<RailVehicle> locomotiveWeak = world->railVehicles->create(Locomotive::classId);
REQUIRE_FALSE(locomotiveWeak.expired());
REQUIRE(world->railVehicles->length == 1);
REQUIRE(world->trains->length == 0);
std::weak_ptr<Train> trainWeak = world->trains->create();
REQUIRE_FALSE(trainWeak.expired());
REQUIRE(world->trains->length == 1);
REQUIRE(trainWeak.lock()->vehicles->length == 0);
trainWeak.lock()->vehicles->add(locomotiveWeak.lock());
REQUIRE(trainWeak.lock()->vehicles->length == 1);
REQUIRE(world->boards->length == 0);
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(world->boards->length == 1);
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg90, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(zoneWeak.lock()->blocks->length == 0);
zoneWeak.lock()->blocks->add(blockWeak.lock());
REQUIRE(zoneWeak.lock()->blocks->length == 1);
REQUIRE(zoneWeak.lock()->trains.size() == 0);
REQUIRE_FALSE(trainWeak.lock()->active);
blockWeak.lock()->assignTrain(trainWeak.lock());
REQUIRE(trainWeak.lock()->active);
REQUIRE(trainWeak.lock()->blocks.size() == 1);
REQUIRE(trainWeak.lock()->zones.size() == 1);
REQUIRE(blockWeak.lock()->trains.size() == 1);
REQUIRE(zoneWeak.lock()->trains.size() == 1);
REQUIRE_FALSE(trainWeak.lock()->mute);
REQUIRE_FALSE(trainWeak.lock()->noSmoke);
REQUIRE_FALSE(locomotiveWeak.lock()->mute);
REQUIRE_FALSE(locomotiveWeak.lock()->noSmoke);
zoneWeak.lock()->mute = true;
REQUIRE(trainWeak.lock()->mute);
REQUIRE(locomotiveWeak.lock()->mute);
zoneWeak.lock()->mute = false;
REQUIRE_FALSE(trainWeak.lock()->mute);
REQUIRE_FALSE(locomotiveWeak.lock()->mute);
zoneWeak.lock()->noSmoke = true;
REQUIRE(trainWeak.lock()->noSmoke);
REQUIRE(locomotiveWeak.lock()->noSmoke);
zoneWeak.lock()->noSmoke = false;
REQUIRE_FALSE(trainWeak.lock()->noSmoke);
REQUIRE_FALSE(locomotiveWeak.lock()->noSmoke);
zoneWeak.lock()->speedLimit.setValue(100.0);
REQUIRE(trainWeak.lock()->speedLimit.value() == Catch::Approx(100.0));
zoneWeak.lock()->speedLimit.setValue(std::numeric_limits<double>::infinity());
REQUIRE(std::isinf(trainWeak.lock()->speedLimit.value()));
zoneWeak.lock()->mute = true;
zoneWeak.lock()->noSmoke = true;
zoneWeak.lock()->speedLimit.setValue(100.0);
blockWeak.lock()->removeTrain(trainWeak.lock());
REQUIRE_FALSE(trainWeak.lock()->mute);
REQUIRE_FALSE(locomotiveWeak.lock()->mute);
REQUIRE_FALSE(trainWeak.lock()->noSmoke);
REQUIRE_FALSE(locomotiveWeak.lock()->noSmoke);
REQUIRE(std::isinf(trainWeak.lock()->speedLimit.value()));
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(locomotiveWeak.expired());
REQUIRE(trainWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(blockWeak.expired());
REQUIRE(zoneWeak.expired());
}
TEST_CASE("Zone: Check class id's", "[zone]")
{
// class id's may NOT be changed, it will break saved worlds and might break client stuff.
REQUIRE(BlockZoneList::classId == "list.block_zone");
REQUIRE(Zone::classId == "zone");
REQUIRE(ZoneBlockList::classId == "list.zone_block");
REQUIRE(ZoneList::classId == "list.zone");
REQUIRE(ZoneListTableModel::classId == "zone_list_table_model");
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
REQUIRE(world->zones->getClassId() == "list.zone");
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg90, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
REQUIRE(blockWeak.lock()->zones->getClassId() == "list.block_zone");
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(zoneWeak.lock()->getClassId() == "zone");
REQUIRE(zoneWeak.lock()->blocks->getClassId() == "list.zone_block");
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(blockWeak.expired());
REQUIRE(zoneWeak.expired());
}
TEST_CASE("Zone: Check enabled attribute", "[zone]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
REQUIRE(world->state.value() == static_cast<WorldState>(0));
REQUIRE_FALSE(world->edit);
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->id));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->name));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->mute));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->noSmoke));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->speedLimit));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->blocks->add));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->blocks->remove));
world->edit = true;
REQUIRE(world->state.value() == WorldState::Edit);
REQUIRE(world->edit);
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->id));
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->name));
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->mute));
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->noSmoke));
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->speedLimit));
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->blocks->add));
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->blocks->remove));
world->run();
REQUIRE(world->state.value() == (WorldState::Edit | WorldState::PowerOn | WorldState::Run));
REQUIRE(world->edit);
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->id));
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->name));
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->mute));
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->noSmoke));
REQUIRE(Attributes::getEnabled(zoneWeak.lock()->speedLimit));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->blocks->add));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->blocks->remove));
world->edit = false;
REQUIRE(world->state.value() == (WorldState::PowerOn | WorldState::Run));
REQUIRE_FALSE(world->edit);
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->id));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->name));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->mute));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->noSmoke));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->speedLimit));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->blocks->add));
REQUIRE_FALSE(Attributes::getEnabled(zoneWeak.lock()->blocks->remove));
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(zoneWeak.expired());
}
TEST_CASE("Zone: zone list table model", "[zone]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
auto zoneListModel = world->zones->getModel();
REQUIRE(zoneListModel);
REQUIRE(zoneListModel->getClassId() == "zone_list_table_model");
REQUIRE(zoneListModel->rowCount() == 0);
std::weak_ptr<Zone> zone1 = world->zones->create();
REQUIRE_FALSE(zone1.expired());
REQUIRE(zoneListModel->rowCount() == 1);
REQUIRE(zoneListModel->getText(0, 0) == zone1.lock()->id.value());
REQUIRE(zoneListModel->getText(1, 0) == zone1.lock()->name.value());
zone1.lock()->id = "zone_one";
REQUIRE(zoneListModel->getText(0, 0) == "zone_one");
zone1.lock()->name = "Zone One";
REQUIRE(zoneListModel->getText(1, 0) == "Zone One");
std::weak_ptr<Zone> zone2 = world->zones->create();
REQUIRE_FALSE(zone2.expired());
REQUIRE(zoneListModel->rowCount() == 2);
REQUIRE(zoneListModel->getText(0, 1) == zone2.lock()->id.value());
REQUIRE(zoneListModel->getText(1, 1) == zone2.lock()->name.value());
zone2.lock()->id = "zone_two";
REQUIRE(zoneListModel->getText(0, 1) == "zone_two");
zone2.lock()->name = "Zone Two";
REQUIRE(zoneListModel->getText(1, 1) == "Zone Two");
world->zones->delete_(zone1.lock());
REQUIRE(zone1.expired());
REQUIRE(zoneListModel->rowCount() == 1);
REQUIRE(zoneListModel->getText(0, 0) == "zone_two");
REQUIRE(zoneListModel->getText(1, 0) == "Zone Two");
REQUIRE(zoneListModel->getText(0, 1) == "");
REQUIRE(zoneListModel->getText(1, 1) == "");
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(zone2.expired());
}
TEST_CASE("Zone: block zone list table model", "[zone]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg90, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
std::weak_ptr<Zone> zone1 = world->zones->create();
REQUIRE_FALSE(zone1.expired());
std::weak_ptr<Zone> zone2 = world->zones->create();
REQUIRE_FALSE(zone2.expired());
auto blockZoneList = blockWeak.lock()->zones->getModel();
REQUIRE(blockZoneList->rowCount() == 0);
blockWeak.lock()->zones->add(zone1.lock());
REQUIRE(blockZoneList->rowCount() == 1);
REQUIRE(blockZoneList->getText(0, 0) == zone1.lock()->id.value());
REQUIRE(blockZoneList->getText(1, 0) == zone1.lock()->name.value());
zone1.lock()->id = "zone_one";
zone1.lock()->name = "Zone One";
REQUIRE(blockZoneList->getText(0, 0) == "zone_one");
REQUIRE(blockZoneList->getText(1, 0) == "Zone One");
zone2.lock()->blocks->add(blockWeak.lock());
REQUIRE(blockZoneList->rowCount() == 2);
REQUIRE(blockZoneList->getText(0, 1) == zone2.lock()->id.value());
REQUIRE(blockZoneList->getText(1, 1) == zone2.lock()->name.value());
zone2.lock()->id = "zone_two";
zone2.lock()->name = "Zone Two";
REQUIRE(blockZoneList->getText(0, 1) == "zone_two");
REQUIRE(blockZoneList->getText(1, 1) == "Zone Two");
blockWeak.lock()->zones->remove(zone1.lock());
REQUIRE(blockZoneList->rowCount() == 1);
REQUIRE(blockZoneList->getText(0, 0) == "zone_two");
REQUIRE(blockZoneList->getText(1, 0) == "Zone Two");
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(blockWeak.expired());
REQUIRE(zone1.expired());
REQUIRE(zone2.expired());
}
TEST_CASE("Zone: zone block list table model", "[zone]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg90, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> block1 = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(block1.expired());
REQUIRE(boardWeak.lock()->addTile(1, 1, TileRotate::Deg0, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> block2 = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({1, 1}));
REQUIRE_FALSE(block2.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
auto zoneBlockList = zoneWeak.lock()->blocks->getModel();
REQUIRE(zoneBlockList->rowCount() == 0);
zoneWeak.lock()->blocks->add(block1.lock());
REQUIRE(zoneBlockList->rowCount() == 1);
REQUIRE(zoneBlockList->getText(0, 0) == block1.lock()->id.value());
REQUIRE(zoneBlockList->getText(1, 0) == block1.lock()->name.value());
block1.lock()->id = "block_one";
block1.lock()->name = "Block One";
REQUIRE(zoneBlockList->getText(0, 0) == "block_one");
REQUIRE(zoneBlockList->getText(1, 0) == "Block One");
zoneWeak.lock()->blocks->add(block2.lock());
REQUIRE(zoneBlockList->rowCount() == 2);
REQUIRE(zoneBlockList->getText(0, 1) == block2.lock()->id.value());
REQUIRE(zoneBlockList->getText(1, 1) == block2.lock()->name.value());
block2.lock()->id = "block_two";
block2.lock()->name = "Block Two";
REQUIRE(zoneBlockList->getText(0, 1) == "block_two");
REQUIRE(zoneBlockList->getText(1, 1) == "Block Two");
zoneWeak.lock()->blocks->remove(block1.lock());
REQUIRE(zoneBlockList->rowCount() == 1);
REQUIRE(zoneBlockList->getText(0, 0) == "block_two");
REQUIRE(zoneBlockList->getText(1, 0) == "Block Two");
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(block1.expired());
REQUIRE(block2.expired());
REQUIRE(zoneWeak.expired());
}
TEST_CASE("Zone: delete zone with block assigned", "[zone]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg90, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
zoneWeak.lock()->blocks->add(blockWeak.lock());
REQUIRE(zoneWeak.lock()->blocks->length.value() == 1);
REQUIRE(blockWeak.lock()->zones->length.value() == 1);
world->zones->delete_(zoneWeak.lock());
REQUIRE(zoneWeak.expired());
REQUIRE(world->zones->length.value() == 0);
REQUIRE(blockWeak.lock()->zones->length.value() == 0);
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(blockWeak.expired());
}
TEST_CASE("Zone: delete block with zone assigned", "[zone]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg90, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
zoneWeak.lock()->blocks->add(blockWeak.lock());
REQUIRE(zoneWeak.lock()->blocks->length.value() == 1);
REQUIRE(blockWeak.lock()->zones->length.value() == 1);
boardWeak.lock()->deleteTile(0, 0);
REQUIRE(blockWeak.expired());
REQUIRE(zoneWeak.lock()->blocks->length.value() == 0);
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(zoneWeak.expired());
}
TEST_CASE("Zone: delete board with block with zone assigned", "[zone]")
{
auto world = World::create();
std::weak_ptr<World> worldWeak = world;
REQUIRE_FALSE(worldWeak.expired());
std::weak_ptr<Board> boardWeak = world->boards->create();
REQUIRE_FALSE(boardWeak.expired());
REQUIRE(boardWeak.lock()->addTile(0, 0, TileRotate::Deg90, BlockRailTile::classId, false));
std::weak_ptr<BlockRailTile> blockWeak = std::dynamic_pointer_cast<BlockRailTile>(boardWeak.lock()->getTile({0, 0}));
REQUIRE_FALSE(blockWeak.expired());
std::weak_ptr<Zone> zoneWeak = world->zones->create();
REQUIRE_FALSE(zoneWeak.expired());
zoneWeak.lock()->blocks->add(blockWeak.lock());
REQUIRE(zoneWeak.lock()->blocks->length.value() == 1);
REQUIRE(blockWeak.lock()->zones->length.value() == 1);
world->boards->delete_(boardWeak.lock());
REQUIRE(boardWeak.expired());
REQUIRE(blockWeak.expired());
REQUIRE(zoneWeak.lock()->blocks->length.value() == 0);
world.reset();
REQUIRE(worldWeak.expired());
REQUIRE(boardWeak.expired());
REQUIRE(zoneWeak.expired());
}

Datei anzeigen

@ -0,0 +1,54 @@
/**
* shared/src/traintastic/enum/zonetrainstate.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_ZONETRAINSTATE_HPP
#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_ZONETRAINSTATE_HPP
#include <cstdint>
#include <array>
#include "enum.hpp"
enum class ZoneTrainState : uint8_t
{
Unknown = 0,
Entering = 1,
Entered = 2,
Leaving = 3,
};
TRAINTASTIC_ENUM(ZoneTrainState, "zone_train_state", 4,
{
{ZoneTrainState::Unknown, "unknown"},
{ZoneTrainState::Entering, "entering"},
{ZoneTrainState::Entered, "entered"},
{ZoneTrainState::Leaving, "leaving"}
});
constexpr std::array<ZoneTrainState, 4> zoneTrainStateValues
{
ZoneTrainState::Unknown,
ZoneTrainState::Entering,
ZoneTrainState::Entered,
ZoneTrainState::Leaving,
};
#endif

Datei anzeigen

@ -6542,6 +6542,30 @@
"comment": "",
"fuzzy": 0
},
{
"term": "world:zones",
"definition": "Zones"
},
{
"term": "zone:blocks",
"definition": "Blocks"
},
{
"term": "zone:mute",
"definition": "Mute"
},
{
"term": "zone:no_smoke",
"definition": "No smoke"
},
{
"term": "list.zone:highlight_zone",
"definition": "Highlight zone"
},
{
"term": "zone:speed_limit",
"definition": "Speed limit"
},
{
"term": "message:I9003",
"definition": "Cleared persistent variables",
@ -6785,5 +6809,9 @@
{
"term": "throttle.controlled_by_x",
"definition": "Controlled by %1"
},
{
"term": "board_tile.rail.block:zones",
"definition": "Zones"
}
]