overrides + arcanist fixes
This commit is contained in:
parent
c8e51f36e2
commit
5a452b220c
|
|
@ -7,6 +7,7 @@ werewolves/img/icons.svg
|
||||||
license_headers.fish
|
license_headers.fish
|
||||||
util/
|
util/
|
||||||
werewolves/Trunk-local.toml
|
werewolves/Trunk-local.toml
|
||||||
|
public/img/icons.svg
|
||||||
|
|
||||||
werewolves-old-client/
|
werewolves-old-client/
|
||||||
werewolves-old-server/
|
werewolves-old-server/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="69.549118mm"
|
||||||
|
height="56.160782mm"
|
||||||
|
viewBox="0 0 69.549118 56.160782"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||||
|
id="defs1" /><g
|
||||||
|
id="layer4"
|
||||||
|
transform="translate(-747.27464,-854.93728)"><g
|
||||||
|
id="g114"><g
|
||||||
|
id="g74-6-0"
|
||||||
|
transform="matrix(0.49535939,0,0,0.49535939,742.24755,744.64487)"><path
|
||||||
|
d="m 41.438814,253.85737 v -11.44064 c -0.10314,0.002 -0.206411,0.006 -0.309542,0.0114 -1.713539,0.0921 -3.402067,0.64565 -4.275191,1.61799 -1.746251,1.94469 -1.477015,6.95296 0.467672,8.69921 0.577742,0.51878 1.869333,0.91432 2.82205,1.08623 0.05301,0.04 0.6259,0.0432 1.295011,0.0258 z m 0,0 v -11.44064 c 0.10314,0.002 0.206411,0.006 0.309542,0.0114 1.713539,0.0921 3.402067,0.64565 4.275191,1.61799 1.746251,1.94469 1.477015,6.95296 -0.467672,8.69921 -0.577742,0.51878 -1.869333,0.91432 -2.82205,1.08623 -0.05301,0.04 -0.6259,0.0432 -1.295011,0.0258 z"
|
||||||
|
style="fill:#0f07ff;stroke:#0f07ff;stroke-width:0.646547"
|
||||||
|
id="path73-2-2" /><path
|
||||||
|
d="m 41.437781,281.85256 v -26.00719 c -0.537817,0.003 -1.074156,0.0344 -1.599386,0.0879 -2.633998,0.53128 -5.422716,2.05847 -6.299357,4.75888 -1.013707,3.1868 -0.65843,6.59772 -0.366903,9.8702 0.3233,3.36773 0.971622,6.70664 1.07487,10.09344 0.63992,0.8794 1.985837,0.77429 2.985348,0.97565 1.39519,0.12946 2.800457,0.21075 4.205428,0.22117 z m 0.0021,0 v -26.00719 c 0.537817,0.003 1.074156,0.0344 1.599386,0.0879 2.633998,0.53128 5.422716,2.05847 6.299357,4.75888 1.013707,3.1868 0.65843,6.59772 0.366903,9.8702 -0.3233,3.36773 -0.971622,6.70664 -1.07487,10.09344 -0.63992,0.8794 -1.985837,0.77429 -2.985348,0.97565 -1.39519,0.12946 -2.800457,0.21075 -4.205428,0.22117 z"
|
||||||
|
style="fill:#0f07ff;stroke:#0f07ff;stroke-width:0.646547"
|
||||||
|
id="path74-6-3" /></g><g
|
||||||
|
id="g74-6-0-0"
|
||||||
|
transform="matrix(0.49535939,0,0,0.49535939,780.79656,744.64437)"><path
|
||||||
|
d="m 41.438814,253.85737 v -11.44064 c -0.10314,0.002 -0.206411,0.006 -0.309542,0.0114 -1.713539,0.0921 -3.402067,0.64565 -4.275191,1.61799 -1.746251,1.94469 -1.477015,6.95296 0.467672,8.69921 0.577742,0.51878 1.869333,0.91432 2.82205,1.08623 0.05301,0.04 0.6259,0.0432 1.295011,0.0258 z m 0,0 v -11.44064 c 0.10314,0.002 0.206411,0.006 0.309542,0.0114 1.713539,0.0921 3.402067,0.64565 4.275191,1.61799 1.746251,1.94469 1.477015,6.95296 -0.467672,8.69921 -0.577742,0.51878 -1.869333,0.91432 -2.82205,1.08623 -0.05301,0.04 -0.6259,0.0432 -1.295011,0.0258 z"
|
||||||
|
style="fill:#0f07ff;stroke:#0f07ff;stroke-width:0.646547"
|
||||||
|
id="path73-2-2-3" /><path
|
||||||
|
d="m 41.437781,281.85256 v -26.00719 c -0.537817,0.003 -1.074156,0.0344 -1.599386,0.0879 -2.633998,0.53128 -5.422716,2.05847 -6.299357,4.75888 -1.013707,3.1868 -0.65843,6.59772 -0.366903,9.8702 0.3233,3.36773 0.971622,6.70664 1.07487,10.09344 0.63992,0.8794 1.985837,0.77429 2.985348,0.97565 1.39519,0.12946 2.800457,0.21075 4.205428,0.22117 z m 0.0021,0 v -26.00719 c 0.537817,0.003 1.074156,0.0344 1.599386,0.0879 2.633998,0.53128 5.422716,2.05847 6.299357,4.75888 1.013707,3.1868 0.65843,6.59772 0.366903,9.8702 -0.3233,3.36773 -0.971622,6.70664 -1.07487,10.09344 -0.63992,0.8794 -1.985837,0.77429 -2.985348,0.97565 -1.39519,0.12946 -2.800457,0.21075 -4.205428,0.22117 z"
|
||||||
|
style="fill:#0f07ff;stroke:#0f07ff;stroke-width:0.646547"
|
||||||
|
id="path74-6-3-0" /></g><g
|
||||||
|
id="g46-3"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="translate(89.910176,11.519392)"><path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#fff800;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:0.595237"
|
||||||
|
d="m 688.06805,869.55518 -15.10948,-20.06719 -15.30685,19.99488"
|
||||||
|
id="path46-0-6" /><path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#fff800;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:0.595237"
|
||||||
|
d="m 672.95522,849.48086 -0.008,20.1008"
|
||||||
|
id="path46-5" /></g><path
|
||||||
|
style="fill:#fffc82;fill-opacity:1;stroke:#67653e;stroke-width:0.734271;stroke-opacity:1"
|
||||||
|
id="path45-6"
|
||||||
|
d="m 777.90756,881.06818 a 15.132905,8.016284 0 0 1 -7.56645,6.9423 15.132905,8.016284 0 0 1 -15.1329,0 15.132905,8.016284 0 0 1 -7.56646,-6.9423 h 15.13291 z" /><g
|
||||||
|
id="g46-3-6"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="translate(128.45917,11.519442)"><path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#fff800;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:0.595237"
|
||||||
|
d="m 688.06805,869.55518 -15.10948,-20.06719 -15.30685,19.99488"
|
||||||
|
id="path46-0-6-1" /><path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#fff800;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:0.595237"
|
||||||
|
d="m 672.95522,849.48086 -0.008,20.1008"
|
||||||
|
id="path46-5-1" /></g><path
|
||||||
|
style="fill:#fffc82;fill-opacity:1;stroke:#67653e;stroke-width:0.734271;stroke-opacity:1"
|
||||||
|
id="path45-6-5"
|
||||||
|
d="m 816.45657,881.06824 a 15.132905,8.016284 0 0 1 -7.56645,6.9423 15.132905,8.016284 0 0 1 -15.1329,0 15.132905,8.016284 0 0 1 -7.56646,-6.9423 h 15.13291 z" /><rect
|
||||||
|
style="fill:#fffa65;fill-opacity:1;stroke:#67653e;stroke-width:1;stroke-opacity:1"
|
||||||
|
id="rect31-9"
|
||||||
|
width="59.770382"
|
||||||
|
height="2.958041"
|
||||||
|
x="752.13153"
|
||||||
|
y="858.67627"
|
||||||
|
rx="11.1125"
|
||||||
|
ry="1.0782195" /><path
|
||||||
|
d="m 781.92294,855.43738 c -1.08829,0 -1.96422,1.53814 -1.96422,3.44888 v 46.49773 c 0,0.0628 7.2e-4,0.12512 0.003,0.18707 h -10.46448 c -6.15632,0 -11.1125,1.12104 -11.1125,2.51354 0,1.3925 -0.0661,2.51355 -0.0661,2.51355 h 47.42657 v -2.51355 c 0,-1.3925 -4.95617,-2.51354 -11.1125,-2.51354 h -10.5606 c 0.002,-0.062 0.003,-0.12426 0.003,-0.18707 v -46.49773 c 0,-1.91074 -0.87593,-3.44888 -1.96422,-3.44888 z"
|
||||||
|
style="fill:#fff965;fill-opacity:1;stroke:#67653e;stroke-opacity:1"
|
||||||
|
id="path33-0" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 6.3 KiB |
5293
public/img/icons.svg
5293
public/img/icons.svg
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 866 KiB |
|
|
@ -0,0 +1,81 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="81.261642mm"
|
||||||
|
height="66.812714mm"
|
||||||
|
viewBox="0 0 81.261642 66.812714"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||||
|
id="defs1" /><g
|
||||||
|
id="layer4"
|
||||||
|
transform="translate(-658.32806,-803.4284)"><g
|
||||||
|
id="g111"><g
|
||||||
|
id="g112"><g
|
||||||
|
id="g106"
|
||||||
|
transform="translate(-1.7773424,0.18708867)"><g
|
||||||
|
id="g100"
|
||||||
|
transform="translate(7.1414687,-15.751776)"><g
|
||||||
|
id="g1-3"
|
||||||
|
transform="matrix(0.44604857,0,0,0.44604857,607.75722,785.83144)"><path
|
||||||
|
id="path148-7"
|
||||||
|
style="fill:#ff2424;fill-opacity:1;stroke:#ff2424;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||||
|
d="m 98.470476,189.15435 c -5.126341,-0.0936 -11.08233,2.61633 -12.495878,8.56846 -1.651423,-6.7938 -9.212596,-9.32255 -14.669906,-8.28166 -5.503903,1.04977 -9.783107,8.15614 -9.656258,13.75782 0.288519,12.74111 22.205993,30.02582 24.425899,31.74431 2.21079,-1.7302 24.034727,-19.12846 24.255887,-31.87092 0.0972,-5.60227 -4.21923,-12.68858 -9.72861,-13.70924 -0.68284,-0.1265 -1.3988,-0.1954 -2.131134,-0.20877 z"
|
||||||
|
transform="translate(50.270833,-38.1)" /><path
|
||||||
|
style="fill:#fff001;fill-opacity:1;stroke:#ff2424;stroke-width:0.15;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path150-4"
|
||||||
|
d="m 84.189903,211.97146 c -1.084141,1.55845 -1.92512,-0.49115 -3.793716,-0.15576 -1.868596,0.33539 -1.944289,2.54952 -3.502742,1.46538 -1.558453,-1.08414 0.491151,-1.92512 0.155762,-3.79371 -0.335389,-1.8686 -2.549524,-1.94429 -1.465383,-3.50275 1.084141,-1.55845 1.925121,0.49116 3.793717,0.15577 1.868596,-0.33539 1.944289,-2.54953 3.502742,-1.46539 1.558452,1.08414 -0.491152,1.92512 -0.155763,3.79372 0.335389,1.8686 2.549524,1.94429 1.465383,3.50274 z"
|
||||||
|
transform="rotate(28.628814,162.35113,272.55182)" /><path
|
||||||
|
style="fill:#fff001;fill-opacity:1;stroke:#ff2424;stroke-width:0.15;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path151-5"
|
||||||
|
d="m 73.525842,205.79755 c -1.802133,1.72521 -2.255654,-1.4788 -4.749977,-1.5277 -2.4873,-0.0488 -3.071191,3.13767 -4.791545,1.34061 -1.725212,-1.80213 1.478793,-2.25565 1.527701,-4.74998 0.04877,-2.4873 -3.137671,-3.07119 -1.340613,-4.79154 1.802133,-1.72521 2.255655,1.47879 4.749978,1.5277 2.487299,0.0488 3.07119,-3.13767 4.791545,-1.34061 1.725212,1.80213 -1.478794,2.25565 -1.527702,4.74997 -0.04877,2.4873 3.137671,3.07119 1.340613,4.79155 z"
|
||||||
|
transform="translate(50.270833,-38.1)" /></g><g
|
||||||
|
id="g46"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="translate(-4.4004781,0.4458306)"><path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#fff800;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:0.595237"
|
||||||
|
d="m 688.06805,869.55518 -15.10948,-20.06719 -15.30685,19.99488"
|
||||||
|
id="path46-0" /><path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#fff800;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:0.595237"
|
||||||
|
d="m 672.95522,849.48086 -0.008,20.1008"
|
||||||
|
id="path46" /></g><path
|
||||||
|
style="fill:#fffc82;fill-opacity:1;stroke:#67653e;stroke-width:0.734271;stroke-opacity:1"
|
||||||
|
id="path45"
|
||||||
|
d="m 683.59689,869.99463 a 15.132905,8.016284 0 0 1 -7.56645,6.9423 15.132905,8.016284 0 0 1 -15.1329,0 15.132905,8.016284 0 0 1 -7.56646,-6.9423 h 15.13291 z" /></g><g
|
||||||
|
id="g104"
|
||||||
|
transform="translate(-5.4255714,1.122532)"><g
|
||||||
|
id="g97"
|
||||||
|
transform="rotate(-107.4873,716.6014,857.88295)"><path
|
||||||
|
d="m 741.45011,864.47313 c -0.61965,0.46041 -4.08011,3.1101 -5.51427,5.86399 l 1.64993,3.62001 -2.43068,-1.72258 c -0.29115,0.61767 -1.3281,3.67079 -1.5476,4.52025 l 1.45896,3.01928 -1.86206,-1.31473 c -0.18852,0.80806 -0.54956,4.17462 -0.55848,5.07496 l 2.31516,2.45068 -2.25264,-0.65679 c 0.13308,2.85064 1.26,5.03306 2.5083,5.34423 0.15431,0.0384 0.31307,0.0535 0.47654,0.045 0.14493,0.0761 0.29668,0.12925 0.45291,0.15891 1.26393,0.23992 3.20053,-1.27052 4.51461,-3.8037 l -2.32328,-0.3465 3.1284,-1.25554 c 0.36875,-0.82142 1.45023,-4.03153 1.61725,-4.8443 l -2.23934,0.41316 2.58842,-2.12932 c 0.15619,-0.86335 0.49096,-4.07031 0.48509,-4.75312 l -2.92603,0.54524 3.01258,-2.59499 c -0.14977,-3.10134 -2.18373,-6.95671 -2.55377,-7.63423 z"
|
||||||
|
style="fill:#ededed;stroke:#676767;stroke-width:0.507712;stroke-opacity:1"
|
||||||
|
id="path85" /><path
|
||||||
|
style="fill:none;stroke:#666666;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 739.51041,895.87916 c -0.77616,1.36482 -6.52512,-3.59608 0.85657,-25.75538"
|
||||||
|
id="path60" /></g><g
|
||||||
|
id="g46-5"
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
transform="translate(56.871733,-35.227408)"><path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#fff800;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:0.596078"
|
||||||
|
d="m 688.06805,869.55518 -15.10948,-28.79844 -15.30685,28.72613"
|
||||||
|
id="path46-0-4" /><path
|
||||||
|
style="fill:none;fill-opacity:1;stroke:#fff800;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:0.596078"
|
||||||
|
d="m 672.95522,840.74961 -0.008,28.83205"
|
||||||
|
id="path46-7" /></g><path
|
||||||
|
style="fill:#fffc82;fill-opacity:1;stroke:#67653e;stroke-width:0.734271;stroke-opacity:1"
|
||||||
|
id="path45-15"
|
||||||
|
d="m 744.86905,834.32141 a 15.132905,8.016284 0 0 1 -7.56645,6.94231 15.132905,8.016284 0 0 1 -15.13291,0 15.132905,8.016284 0 0 1 -7.56645,-6.94231 h 15.13291 z" /></g><rect
|
||||||
|
style="fill:#fffa65;fill-opacity:1;stroke:#67653e;stroke-width:1;stroke-opacity:1"
|
||||||
|
id="rect31"
|
||||||
|
width="59.770382"
|
||||||
|
height="2.958041"
|
||||||
|
x="167.49957"
|
||||||
|
y="1058.3093"
|
||||||
|
rx="11.1125"
|
||||||
|
ry="1.0782195"
|
||||||
|
transform="rotate(-30)" /></g><path
|
||||||
|
d="m 700.74059,814.58039 c -1.08829,0 -1.96422,1.53814 -1.96422,3.44888 V 864.527 c 0,0.0628 7.2e-4,0.12512 0.003,0.18707 h -10.46448 c -6.15632,0 -11.1125,1.12104 -11.1125,2.51354 0,1.3925 -0.0661,2.51355 -0.0661,2.51355 h 47.42657 v -2.51355 c 0,-1.3925 -4.95617,-2.51354 -11.1125,-2.51354 h -10.5606 c 0.002,-0.062 0.003,-0.12426 0.003,-0.18707 v -46.49773 c 0,-1.91074 -0.87593,-3.44888 -1.96422,-3.44888 z"
|
||||||
|
style="fill:#fff965;fill-opacity:1;stroke:#67653e;stroke-opacity:1"
|
||||||
|
id="path33" /></g></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 7.1 KiB |
|
|
@ -17,4 +17,5 @@
|
||||||
|
|
||||||
.icon-shrink {
|
.icon-shrink {
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
|
height: 1em;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
140
style/main.scss
140
style/main.scss
|
|
@ -185,6 +185,10 @@ nav.header {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
&[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
|
|
||||||
.username {
|
.username {
|
||||||
|
|
@ -292,6 +296,23 @@ dialog::backdrop {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.host-in-game-wrapper {
|
||||||
|
z-index: 1;
|
||||||
|
background-color: black;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
margin: 0;
|
||||||
|
position: fixed;
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
&>div,
|
||||||
|
&>nav {
|
||||||
|
padding: 1ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#change-password,
|
#change-password,
|
||||||
#update-profile {
|
#update-profile {
|
||||||
.pwless-notice {
|
.pwless-notice {
|
||||||
|
|
@ -999,6 +1020,10 @@ form {
|
||||||
gap: 0.5ch;
|
gap: 0.5ch;
|
||||||
padding-bottom: 1ch;
|
padding-bottom: 1ch;
|
||||||
|
|
||||||
|
max-height: 75vh;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
|
||||||
.character {
|
.character {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -1028,3 +1053,118 @@ form {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overrides-screen {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
width: 80vw;
|
||||||
|
margin-left: 10vw;
|
||||||
|
margin-right: 10vw;
|
||||||
|
|
||||||
|
.override-selector {
|
||||||
|
.category {
|
||||||
|
.selected {
|
||||||
|
background-color: rgba(0, 255, 0, 0.7);
|
||||||
|
border: 1px solid rgba(0, 255, 0, 1.0);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:not(:hover) {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
max-width: 80vw;
|
||||||
|
|
||||||
|
.overrides-screens {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.prompt-overrides,
|
||||||
|
.result-overrides {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-width: 40vw;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.close {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
font-size: 2em;
|
||||||
|
|
||||||
|
.result-number {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-options,
|
||||||
|
.prompt-options {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
|
||||||
|
.option-set {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-option,
|
||||||
|
.prompt-option {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt-number {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
gap: 1ch;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding-left: 0.5ch;
|
||||||
|
padding-right: 0.5ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,8 @@
|
||||||
|
|
||||||
font-size: 1.75em;
|
font-size: 1.75em;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
.subtext {
|
.subtext {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
|
|
@ -71,6 +73,9 @@
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: block;
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 2ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -123,17 +128,53 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5ch;
|
gap: 0.5ch;
|
||||||
|
row-gap: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
|
.icon-fit {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.bool-picker {
|
||||||
|
width: calc(100% - 6ch);
|
||||||
|
height: calc(100% - 6ch);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
padding: 3ch;
|
||||||
|
gap: 3ch;
|
||||||
|
|
||||||
|
&>button {
|
||||||
|
font-size: 3em;
|
||||||
|
width: 30vw;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
background-color: color.change($red1, $alpha: 0.1);
|
||||||
|
border: 1px solid color.change($red1, $alpha: 0.6);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: color.change($blue1, $alpha: 0.3);
|
||||||
|
border: 1px solid $blue1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.target-picker {
|
.target-picker {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
&.allow-scroll {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
justify-content: unset;
|
||||||
|
}
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
|
|
||||||
|
|
@ -161,6 +202,12 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
gap: 10%;
|
gap: 10%;
|
||||||
|
|
||||||
|
@media only screen and (min-width : 1200px) {
|
||||||
|
&>img {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.two-column {
|
.two-column {
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,8 @@ decl_icon!(
|
||||||
Mason: "/img/mason.svg",
|
Mason: "/img/mason.svg",
|
||||||
NotEqual: "/img/not-equal.svg",
|
NotEqual: "/img/not-equal.svg",
|
||||||
Equal: "/img/equal.svg",
|
Equal: "/img/equal.svg",
|
||||||
|
UnbalancedScales: "/img/unbalanced-scales.svg",
|
||||||
|
BalancedScales: "/img/balanced-scales.svg",
|
||||||
RedX: "/img/red-x.svg",
|
RedX: "/img/red-x.svg",
|
||||||
Damned: "/img/damned.svg",
|
Damned: "/img/damned.svg",
|
||||||
Bloodlet: "/img/bloodlet.svg",
|
Bloodlet: "/img/bloodlet.svg",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
use core::ops::{Add, AddAssign, Not, RangeInclusive, SubAssign};
|
||||||
|
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn IncDecU8(
|
||||||
|
value: RwSignal<u8>,
|
||||||
|
#[prop(default = 0..=0xFFu8)] value_range: RangeInclusive<u8>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let dec_disabled = {
|
||||||
|
let value_range = value_range.clone();
|
||||||
|
move || !value_range.contains(&value.get().saturating_sub(1))
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="inc-dec">
|
||||||
|
<button
|
||||||
|
on:click=move |_| value.set(value.get().saturating_sub(1))
|
||||||
|
disabled=dec_disabled
|
||||||
|
>
|
||||||
|
"-"
|
||||||
|
</button>
|
||||||
|
<span class="value">{move || value.get()}</span>
|
||||||
|
<button
|
||||||
|
on:click=move |_| value.set(value.get().saturating_add(1))
|
||||||
|
disabled=move || !value_range.contains(&value.get().saturating_add(1))
|
||||||
|
>
|
||||||
|
"+"
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Increment: Copy + AddAssign<Self> {
|
||||||
|
fn increment(self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Decrement: Copy + SubAssign<Self> {
|
||||||
|
fn decrement(self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! inc_dec_impl {
|
||||||
|
($($n:ty),*) => {
|
||||||
|
$(
|
||||||
|
impl Increment for $n {
|
||||||
|
fn increment(self) -> Self {
|
||||||
|
self.saturating_add(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Decrement for $n {
|
||||||
|
fn decrement(self) -> Self {
|
||||||
|
self.saturating_sub(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
};
|
||||||
|
}
|
||||||
|
inc_dec_impl!(
|
||||||
|
u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
|
||||||
|
);
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn IncDec<V>(
|
||||||
|
value: RwSignal<V>,
|
||||||
|
#[prop(default = V::default()..=V::default().not())] value_range: RangeInclusive<V>,
|
||||||
|
) -> impl IntoView
|
||||||
|
where
|
||||||
|
V: Increment
|
||||||
|
+ Decrement
|
||||||
|
+ Eq
|
||||||
|
+ Ord
|
||||||
|
+ Not<Output = V>
|
||||||
|
+ Default
|
||||||
|
+ Send
|
||||||
|
+ Sync
|
||||||
|
+ ToString
|
||||||
|
+ 'static,
|
||||||
|
{
|
||||||
|
let dec_disabled = {
|
||||||
|
let value_range = value_range.clone();
|
||||||
|
move || !value_range.contains(&value.get().decrement())
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="inc-dec">
|
||||||
|
<button on:click=move |_| value.set(value.get().decrement()) disabled=dec_disabled>
|
||||||
|
"-"
|
||||||
|
</button>
|
||||||
|
<span class="value">{move || value.get().to_string()}</span>
|
||||||
|
<button
|
||||||
|
on:click=move |_| value.set(value.get().increment())
|
||||||
|
disabled=move || !value_range.contains(&value.get().increment())
|
||||||
|
>
|
||||||
|
"+"
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,11 +4,17 @@ use leptos::{ev::MouseEvent, prelude::*};
|
||||||
use leptos_router::hooks::use_url;
|
use leptos_router::hooks::use_url;
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use werewolves_proto::{error::ServerError, game::GameId, token::TokenString};
|
use werewolves_proto::{
|
||||||
|
error::ServerError,
|
||||||
|
game::GameId,
|
||||||
|
message::host::{HostGameMessage, HostMessage, HostNightMessage},
|
||||||
|
token::TokenString,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
components::LinkButton,
|
components::LinkButton,
|
||||||
|
pages::host::HostPage,
|
||||||
storage::user::{AuthContext, AuthContextStoreFields},
|
storage::user::{AuthContext, AuthContextStoreFields},
|
||||||
},
|
},
|
||||||
db::AppState,
|
db::AppState,
|
||||||
|
|
@ -140,3 +146,46 @@ fn is_big_screen_path(path: &str) -> bool {
|
||||||
&& Uuid::parse_str(parts[1]).is_ok()
|
&& Uuid::parse_str(parts[1]).is_ok()
|
||||||
&& parts[2] == "big"
|
&& parts[2] == "big"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn HostInGameNav(
|
||||||
|
reply: WriteSignal<Option<HostMessage>>,
|
||||||
|
night: ReadSignal<bool>,
|
||||||
|
show_back_button_only: RwSignal<bool>,
|
||||||
|
overrides_set: RwSignal<bool>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let previous = move |_| reply.set(Some(HostMessage::InGame(HostGameMessage::PreviousState)));
|
||||||
|
let skip = move |_| {
|
||||||
|
reply.set(Some(HostMessage::InGame(HostGameMessage::Night(
|
||||||
|
HostNightMessage::SkipAction,
|
||||||
|
))))
|
||||||
|
};
|
||||||
|
let back = move |_| {
|
||||||
|
show_back_button_only.set(false);
|
||||||
|
overrides_set.set(false);
|
||||||
|
reply.set(Some(HostMessage::GetState));
|
||||||
|
};
|
||||||
|
let hide_if_not_back_button = move || show_back_button_only.get();
|
||||||
|
let hide_if_not_night = move || show_back_button_only.get() || !night.get();
|
||||||
|
view! {
|
||||||
|
<nav class="header" hidden=hide_if_not_night>
|
||||||
|
<button on:click=back hidden=move || !show_back_button_only.get()>
|
||||||
|
"back"
|
||||||
|
</button>
|
||||||
|
<button on:click=previous hidden=hide_if_not_night>
|
||||||
|
"previous"
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
on:click=move |_| {
|
||||||
|
overrides_set.set(true);
|
||||||
|
}
|
||||||
|
hidden=move || show_back_button_only.get() || !night.get()
|
||||||
|
>
|
||||||
|
"overrides"
|
||||||
|
</button>
|
||||||
|
<button on:click=skip hidden=hide_if_not_night>
|
||||||
|
"skip"
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
pub mod big;
|
pub mod big;
|
||||||
mod host;
|
pub mod host;
|
||||||
mod player;
|
mod player;
|
||||||
|
|
||||||
use codee::binary::MsgpackSerdeCodec;
|
use codee::binary::MsgpackSerdeCodec;
|
||||||
|
|
@ -10,6 +10,7 @@ use leptos_use::{
|
||||||
use_websocket_with_options,
|
use_websocket_with_options,
|
||||||
};
|
};
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
|
use werewolves_proto::message::host::ServerToHostMessage;
|
||||||
use werewolves_proto::message::{ClientMessage, host::HostMessage};
|
use werewolves_proto::message::{ClientMessage, host::HostMessage};
|
||||||
use werewolves_proto::message::{IntoClientResponse, WrappedServerMessage};
|
use werewolves_proto::message::{IntoClientResponse, WrappedServerMessage};
|
||||||
|
|
||||||
|
|
@ -91,7 +92,18 @@ pub fn GamePage(error: WriteSignal<Option<WolfError>>) -> impl IntoView {
|
||||||
}
|
}
|
||||||
match message.clone() {
|
match message.clone() {
|
||||||
Some(IntoClientResponse::Host(host_msg)) => {
|
Some(IntoClientResponse::Host(host_msg)) => {
|
||||||
log::debug!("got host message: {:?}", host_msg.title());
|
match &host_msg {
|
||||||
|
ServerToHostMessage::ActionPrompt(prompt, page) => {
|
||||||
|
log::debug!(
|
||||||
|
"got host message: ActionPrompt({page}, {:?})",
|
||||||
|
prompt.title()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ServerToHostMessage::ActionResult(_, result) => {
|
||||||
|
log::debug!("got host message: ActionResult({:?})", result.title())
|
||||||
|
}
|
||||||
|
_ => log::debug!("got host message: {:?}", host_msg.title()),
|
||||||
|
};
|
||||||
host_message.set(Some(host_msg));
|
host_message.set(Some(host_msg));
|
||||||
}
|
}
|
||||||
Some(IntoClientResponse::Player(player_msg)) => {
|
Some(IntoClientResponse::Player(player_msg)) => {
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,7 @@ pub fn BigScreen() -> impl IntoView {
|
||||||
<div class:big-screen-wrapper=move || {
|
<div class:big-screen-wrapper=move || {
|
||||||
!matches!(page.get(), BigScreenPage::Setup)
|
!matches!(page.get(), BigScreenPage::Setup)
|
||||||
}>{content}</div>
|
}>{content}</div>
|
||||||
}.into_any()
|
}
|
||||||
|
.into_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
werewolves_macros::include_path!("werewolves/src/app/pages/game/host");
|
werewolves_macros::include_path!("werewolves/src/app/pages/game/host");
|
||||||
|
mod overrides {
|
||||||
|
werewolves_macros::include_path!("werewolves/src/app/pages/game/host/overrides");
|
||||||
|
}
|
||||||
|
|
||||||
use core::num::NonZeroU8;
|
use core::{num::NonZeroU8, ops::Not};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
|
@ -16,13 +19,17 @@ use werewolves_proto::{
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
Preferences,
|
Preferences,
|
||||||
components::DialogModal,
|
components::{DialogModal, HostInGameNav},
|
||||||
pages::night_actions::{RolePrompt, RoleResult},
|
pages::{
|
||||||
|
game::host::overrides::Overrides,
|
||||||
|
host::overrides::OverrideScreen,
|
||||||
|
night_actions::{RolePrompt, RoleResult},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use crate::{ConsoleLogError, app::error::WolfError};
|
use crate::{ConsoleLogError, app::error::WolfError};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Default)]
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
enum HostPage {
|
pub enum HostPage {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
Settings,
|
Settings,
|
||||||
|
|
@ -56,6 +63,13 @@ pub fn HostGamePage(
|
||||||
let players: RwSignal<Box<[PlayerState]>> = RwSignal::new(Box::new([]));
|
let players: RwSignal<Box<[PlayerState]>> = RwSignal::new(Box::new([]));
|
||||||
let dialog_open = RwSignal::new(HashMap::new());
|
let dialog_open = RwSignal::new(HashMap::new());
|
||||||
let acks: RwSignal<Box<[RoleRevealCharacter]>> = RwSignal::new(Box::new([]));
|
let acks: RwSignal<Box<[RoleRevealCharacter]>> = RwSignal::new(Box::new([]));
|
||||||
|
let night = RwSignal::new(false);
|
||||||
|
let show_back_button_only = RwSignal::new(false);
|
||||||
|
let on_overrides_screen = RwSignal::new(false);
|
||||||
|
let overrides_screen: RwSignal<Option<OverrideScreen>> = RwSignal::new(None);
|
||||||
|
let override_page_number = RwSignal::new(0usize);
|
||||||
|
let prompt_page = RwSignal::new(0usize);
|
||||||
|
let target_picker_count = RwSignal::new(16u8);
|
||||||
|
|
||||||
let open_categories = RwSignal::new(
|
let open_categories = RwSignal::new(
|
||||||
Category::ALL
|
Category::ALL
|
||||||
|
|
@ -115,6 +129,7 @@ pub fn HostGamePage(
|
||||||
page.set(HostPage::RoleRevealAcks);
|
page.set(HostPage::RoleRevealAcks);
|
||||||
}
|
}
|
||||||
Srv2Host::ActionPrompt(prompt, prompt_page) => {
|
Srv2Host::ActionPrompt(prompt, prompt_page) => {
|
||||||
|
override_page_number.set(prompt_page);
|
||||||
page.set(HostPage::ActionPrompt {
|
page.set(HostPage::ActionPrompt {
|
||||||
prompt,
|
prompt,
|
||||||
page: prompt_page,
|
page: prompt_page,
|
||||||
|
|
@ -146,7 +161,27 @@ pub fn HostGamePage(
|
||||||
.is_some()
|
.is_some()
|
||||||
.then_some(view! { <CancelGame reply=reply prefs=prefs /> })
|
.then_some(view! { <CancelGame reply=reply prefs=prefs /> })
|
||||||
};
|
};
|
||||||
let content = move || match page.get() {
|
Effect::new(move || match page.get() {
|
||||||
|
HostPage::None
|
||||||
|
| HostPage::Settings
|
||||||
|
| HostPage::RoleRevealAcks
|
||||||
|
| HostPage::Daytime { .. } => night.set(false),
|
||||||
|
HostPage::ActionPrompt { .. } | HostPage::ActionResult { .. } => night.set(true),
|
||||||
|
});
|
||||||
|
let content = move || {
|
||||||
|
if on_overrides_screen.get() {
|
||||||
|
show_back_button_only.set(true);
|
||||||
|
return view! {
|
||||||
|
<Overrides
|
||||||
|
reply=reply
|
||||||
|
screen=overrides_screen
|
||||||
|
page=prompt_page
|
||||||
|
target_picker_count=target_picker_count
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
.into_any();
|
||||||
|
}
|
||||||
|
match page.get() {
|
||||||
HostPage::None => ().into_any(),
|
HostPage::None => ().into_any(),
|
||||||
HostPage::Settings => view! {
|
HostPage::Settings => view! {
|
||||||
<Settings
|
<Settings
|
||||||
|
|
@ -188,14 +223,39 @@ pub fn HostGamePage(
|
||||||
characters,
|
characters,
|
||||||
marked,
|
marked,
|
||||||
reply,
|
reply,
|
||||||
} => view! {
|
} => {
|
||||||
<DaytimePlayerList day=day characters=characters marked=marked reply=reply/>
|
view! { <DaytimePlayerList day=day characters=characters marked=marked reply=reply /> }
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
move || match page.get() {
|
||||||
|
HostPage::Settings | HostPage::None => view! {
|
||||||
|
{cancel}
|
||||||
|
{content}
|
||||||
}
|
}
|
||||||
.into_any(),
|
.into_any(),
|
||||||
};
|
HostPage::RoleRevealAcks
|
||||||
view! {
|
| HostPage::ActionPrompt { .. }
|
||||||
{cancel}
|
| HostPage::ActionResult { .. }
|
||||||
{content}
|
| HostPage::Daytime { .. } => {
|
||||||
|
let cancel = matches!(&*page.read(), HostPage::Daytime { .. })
|
||||||
|
.not()
|
||||||
|
.then_some(cancel);
|
||||||
|
view! {
|
||||||
|
<div class="host-in-game-wrapper">
|
||||||
|
<HostInGameNav
|
||||||
|
reply=reply
|
||||||
|
night=night.read_only()
|
||||||
|
show_back_button_only=show_back_button_only
|
||||||
|
overrides_set=on_overrides_screen
|
||||||
|
/>
|
||||||
|
<div>{cancel} {content}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ pub fn DaytimePlayerList(
|
||||||
.collect::<Box<[_]>>();
|
.collect::<Box<[_]>>();
|
||||||
kills.is_empty().not().then_some(view! {
|
kills.is_empty().not().then_some(view! {
|
||||||
<div class="info-tidbit">
|
<div class="info-tidbit">
|
||||||
<label>{"died last night"}</label>
|
<span class="breakable">"died last night"</span>
|
||||||
<div class="last-nights-kills">{kills.into_iter().collect_view()}</div>
|
<div class="last-nights-kills">{kills.into_iter().collect_view()}</div>
|
||||||
</div>
|
</div>
|
||||||
})
|
})
|
||||||
|
|
@ -127,10 +127,7 @@ pub fn DaytimePlayerList(
|
||||||
<DialogModal button_content=button_text.clone()>
|
<DialogModal button_content=button_text.clone()>
|
||||||
<h3>{confirmation_text.clone()}</h3>
|
<h3>{confirmation_text.clone()}</h3>
|
||||||
<button on:click=move |_| {
|
<button on:click=move |_| {
|
||||||
reply
|
reply.set(Some(HostMessage::InGame(HostGameMessage::Day(HostDayMessage::Execute))))
|
||||||
.set(
|
|
||||||
Some(HostMessage::InGame(HostGameMessage::Day(HostDayMessage::Execute))),
|
|
||||||
)
|
|
||||||
}>{button_text.clone()}</button>
|
}>{button_text.clone()}</button>
|
||||||
</DialogModal>
|
</DialogModal>
|
||||||
};
|
};
|
||||||
|
|
@ -174,7 +171,12 @@ fn DaytimePlayer(
|
||||||
let text = role.to_string().to_case(Case::Title);
|
let text = role.to_string().to_case(Case::Title);
|
||||||
let align_class = role.wolf().then_some("red");
|
let align_class = role.wolf().then_some("red");
|
||||||
view! {
|
view! {
|
||||||
<button on:click=select class="character no-hover" class:dead=died_to.is_some() class:marked=marked>
|
<button
|
||||||
|
on:click=select
|
||||||
|
class="character no-hover"
|
||||||
|
class:dead=died_to.is_some()
|
||||||
|
class:marked=marked
|
||||||
|
>
|
||||||
<div class="day-char">
|
<div class="day-char">
|
||||||
<span class="headline">
|
<span class="headline">
|
||||||
<Icon source=icon r#type=IconType::Small />
|
<Icon source=icon r#type=IconType::Small />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,452 @@
|
||||||
|
// Copyright (C) 2025-2026 Emilis Bliūdžius
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use core::num::NonZeroU8;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
use werewolves_proto::message::host::HostMessage;
|
||||||
|
use werewolves_proto::{
|
||||||
|
character::CharacterId,
|
||||||
|
diedto::DiedToTitle,
|
||||||
|
message::{
|
||||||
|
CharacterIdentity,
|
||||||
|
host::ServerToHostMessage,
|
||||||
|
night::{ActionPrompt, ActionPromptTitle, ActionResult, ActionResultTitle, Visits},
|
||||||
|
},
|
||||||
|
role::{Alignment, AlignmentEq, Killer, Powerful, RoleTitle},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::app::class::AsClasses;
|
||||||
|
use crate::app::components::IncDecU8;
|
||||||
|
use crate::app::pages::game::host::overrides::{PromptScreenTest, ResultScreenTest};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn Overrides(
|
||||||
|
reply: WriteSignal<Option<HostMessage>>,
|
||||||
|
screen: RwSignal<Option<OverrideScreen>>,
|
||||||
|
page: RwSignal<usize>,
|
||||||
|
target_picker_count: RwSignal<u8>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let internal_send: RwSignal<Option<ServerToHostMessage>> = RwSignal::new(None);
|
||||||
|
let prompt: RwSignal<Option<ActionPrompt>> = RwSignal::new(None);
|
||||||
|
Effect::new(move || {
|
||||||
|
let Some(msg) = internal_send.get() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
match &msg {
|
||||||
|
ServerToHostMessage::ActionPrompt(prompt, p) => {
|
||||||
|
page.set(*p);
|
||||||
|
screen.set(Some(OverrideScreen::Prompt(prompt.clone())))
|
||||||
|
}
|
||||||
|
ServerToHostMessage::ActionResult(_, result) => {
|
||||||
|
screen.set(Some(OverrideScreen::Result(result.clone())))
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
reply.set(Some(HostMessage::Echo(msg)));
|
||||||
|
});
|
||||||
|
Effect::new(move || {
|
||||||
|
if let Some(OverrideScreen::Prompt(p)) = screen.get() {
|
||||||
|
prompt.set(Some(p));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let screen_opts = move || {
|
||||||
|
screen.get().map(|screen| match screen {
|
||||||
|
OverrideScreen::Prompt(p) => {
|
||||||
|
view! { <PromptScreenTest prompt=prompt page=page send=internal_send.write_only() /> }
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
OverrideScreen::Result(result) => {
|
||||||
|
view! { <ResultScreenTest result=result.clone() send=internal_send.write_only() /> }
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
OverrideScreen::TargetPicker(_) => {
|
||||||
|
Effect::new(move || reply.set(Some(HostMessage::Echo(ServerToHostMessage::ActionPrompt(ActionPrompt::WolfPackKill { living_villagers: identities(target_picker_count.get() as _), marked: None }, usize::MAX)))));
|
||||||
|
view! {
|
||||||
|
<span>"targets"</span>
|
||||||
|
<IncDecU8 value=target_picker_count value_range=1..=0xFFu8 />
|
||||||
|
}.into_any()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="overrides-screen">
|
||||||
|
<TestScreenSelector screen=screen send=internal_send.write_only() />
|
||||||
|
<button on:click=move |_| {
|
||||||
|
screen.set(Some(OverrideScreen::TargetPicker(identities(16))))
|
||||||
|
}>"target picker"</button>
|
||||||
|
{screen_opts}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn TestScreenSelector(
|
||||||
|
screen: RwSignal<Option<OverrideScreen>>,
|
||||||
|
send: WriteSignal<Option<ServerToHostMessage>>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let prompts = move || {
|
||||||
|
ActionPromptTitle::ALL
|
||||||
|
.into_iter()
|
||||||
|
.map(|title| {
|
||||||
|
let OverrideScreen::Prompt(prompt) = Into::<OverrideScreen>::into(title) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let picked_class = if let Some(OverrideScreen::Prompt(current)) = screen.get()
|
||||||
|
&& current.title() == title
|
||||||
|
{
|
||||||
|
Some("selected")
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let callback = move |_| {
|
||||||
|
let OverrideScreen::Prompt(prompt) = Into::<OverrideScreen>::into(title) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
screen.set(Some(OverrideScreen::Prompt(prompt.clone())));
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(prompt, 0)));
|
||||||
|
};
|
||||||
|
let class = prompt_class(&prompt);
|
||||||
|
view! {
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
on:click=callback
|
||||||
|
class=[class, picked_class, Some("hover"), Some("box")].as_classes()
|
||||||
|
>
|
||||||
|
{format!("{title:?}")}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_view()
|
||||||
|
};
|
||||||
|
|
||||||
|
let results = move || {
|
||||||
|
ActionResultTitle::ALL
|
||||||
|
.into_iter()
|
||||||
|
.filter(|title| !matches!(title, ActionResultTitle::Continue))
|
||||||
|
.map(|title| {
|
||||||
|
let OverrideScreen::Result(result) = Into::<OverrideScreen>::into(title) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let picked_class = if let Some(OverrideScreen::Result(current)) = screen.get()
|
||||||
|
&& current.title() == title
|
||||||
|
{
|
||||||
|
Some("selected")
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let callback = move |_| {
|
||||||
|
let OverrideScreen::Result(result) = Into::<OverrideScreen>::into(title) else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
screen.set(Some(OverrideScreen::Result(result.clone())));
|
||||||
|
send.set(Some(ServerToHostMessage::ActionResult(None, result)));
|
||||||
|
};
|
||||||
|
let class = result_class(&result);
|
||||||
|
view! {
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
on:click=callback
|
||||||
|
class=[picked_class, class, Some("hover"), Some("box")].as_classes()
|
||||||
|
>
|
||||||
|
{format!("{title:?}")}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_view()
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="override-selector">
|
||||||
|
<div class="category">
|
||||||
|
<span>"prompts"</span>
|
||||||
|
<ul class="overrides-screens">{prompts}</ul>
|
||||||
|
</div>
|
||||||
|
<div class="category">
|
||||||
|
<span>"results"</span>
|
||||||
|
<ul class="overrides-screens">{results}</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn identities(num: usize) -> Box<[CharacterIdentity]> {
|
||||||
|
(1..=num)
|
||||||
|
.map(|num| {
|
||||||
|
CharacterIdentity::new(
|
||||||
|
CharacterId::from_u128(num as _),
|
||||||
|
format!("Player {num}"),
|
||||||
|
Some("they/them".into()),
|
||||||
|
NonZeroU8::new(num as _).unwrap(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn identity() -> CharacterIdentity {
|
||||||
|
identities(1).into_iter().next().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum OverrideScreen {
|
||||||
|
Prompt(ActionPrompt),
|
||||||
|
Result(ActionResult),
|
||||||
|
TargetPicker(Box<[CharacterIdentity]>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ActionResultTitle> for OverrideScreen {
|
||||||
|
fn from(value: ActionResultTitle) -> Self {
|
||||||
|
OverrideScreen::Result(match value {
|
||||||
|
ActionResultTitle::SkippedByHost => ActionResult::SkippedByHost,
|
||||||
|
ActionResultTitle::RoleBlocked => ActionResult::RoleBlocked,
|
||||||
|
ActionResultTitle::Drunk => ActionResult::Drunk,
|
||||||
|
ActionResultTitle::Seer => ActionResult::Seer(identity(), Alignment::Village),
|
||||||
|
ActionResultTitle::PowerSeer => ActionResult::PowerSeer {
|
||||||
|
target: identity(),
|
||||||
|
powerful: Powerful::Powerful,
|
||||||
|
},
|
||||||
|
ActionResultTitle::Adjudicator => ActionResult::Adjudicator {
|
||||||
|
target: identity(),
|
||||||
|
killer: Killer::Killer,
|
||||||
|
},
|
||||||
|
ActionResultTitle::Arcanist => ActionResult::Arcanist(
|
||||||
|
(identity(), identities(2).last().cloned().unwrap()),
|
||||||
|
AlignmentEq::Same,
|
||||||
|
),
|
||||||
|
ActionResultTitle::GraveDigger => ActionResult::GraveDigger(identity(), None),
|
||||||
|
ActionResultTitle::Mortician => {
|
||||||
|
ActionResult::Mortician(identity(), DiedToTitle::Execution)
|
||||||
|
}
|
||||||
|
ActionResultTitle::Insomniac => ActionResult::Insomniac(Visits::new(identities(2))),
|
||||||
|
ActionResultTitle::Empath => ActionResult::Empath {
|
||||||
|
target: identity(),
|
||||||
|
scapegoat: true,
|
||||||
|
},
|
||||||
|
ActionResultTitle::BeholderSawNothing => ActionResult::BeholderSawNothing,
|
||||||
|
ActionResultTitle::BeholderSawEverything => ActionResult::BeholderSawEverything,
|
||||||
|
ActionResultTitle::GoBackToSleep => ActionResult::GoBackToSleep,
|
||||||
|
ActionResultTitle::ShiftFailed => ActionResult::ShiftFailed,
|
||||||
|
ActionResultTitle::Continue => ActionResult::Continue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ActionPromptTitle> for OverrideScreen {
|
||||||
|
fn from(value: ActionPromptTitle) -> Self {
|
||||||
|
Self::Prompt(match value {
|
||||||
|
ActionPromptTitle::BeholderWakes => ActionPrompt::BeholderWakes {
|
||||||
|
character_id: identity(),
|
||||||
|
},
|
||||||
|
ActionPromptTitle::CoverOfDarkness => ActionPrompt::CoverOfDarkness,
|
||||||
|
ActionPromptTitle::WolvesIntro => ActionPrompt::WolvesIntro {
|
||||||
|
wolves: identities(5)
|
||||||
|
.into_iter()
|
||||||
|
.zip([
|
||||||
|
RoleTitle::Werewolf,
|
||||||
|
RoleTitle::AlphaWolf,
|
||||||
|
RoleTitle::DireWolf,
|
||||||
|
RoleTitle::LoneWolf,
|
||||||
|
RoleTitle::Bloodletter,
|
||||||
|
])
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
|
ActionPromptTitle::RoleChange => ActionPrompt::RoleChange {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
new_role: RoleTitle::Adjudicator,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::ElderReveal => ActionPrompt::ElderReveal {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Seer => ActionPrompt::Seer {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Protector => ActionPrompt::Protector {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
targets: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Arcanist => ActionPrompt::Arcanist {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: (None, None),
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Gravedigger => ActionPrompt::Gravedigger {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
dead_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Hunter => ActionPrompt::Hunter {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
current_target: None,
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Militia => ActionPrompt::Militia {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::MapleWolf => ActionPrompt::MapleWolf {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
nights_til_starvation: 0,
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Guardian => ActionPrompt::Guardian {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
previous: None,
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Adjudicator => ActionPrompt::Adjudicator {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::PowerSeer => ActionPrompt::PowerSeer {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Mortician => ActionPrompt::Mortician {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
dead_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::BeholderChooses => ActionPrompt::BeholderChooses {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::MasonsWake => ActionPrompt::MasonsWake {
|
||||||
|
leader: identities(1).into_iter().next().unwrap(),
|
||||||
|
masons: identities(3),
|
||||||
|
},
|
||||||
|
ActionPromptTitle::MasonLeaderRecruit => ActionPrompt::MasonLeaderRecruit {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
recruits_left: NonZeroU8::new(3).unwrap(),
|
||||||
|
potential_recruits: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Empath => ActionPrompt::Empath {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Vindicator => ActionPrompt::Vindicator {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::PyreMaster => ActionPrompt::PyreMaster {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::WolfPackKill => ActionPrompt::WolfPackKill {
|
||||||
|
living_villagers: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Shapeshifter => ActionPrompt::Shapeshifter {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
},
|
||||||
|
ActionPromptTitle::AlphaWolf => ActionPrompt::AlphaWolf {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_villagers: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::DireWolf => ActionPrompt::DireWolf {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::LoneWolfKill => ActionPrompt::LoneWolfKill {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Insomniac => ActionPrompt::Insomniac {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
},
|
||||||
|
ActionPromptTitle::Bloodletter => ActionPrompt::Bloodletter {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
living_players: identities(20),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
|
ActionPromptTitle::DamnedIntro => ActionPrompt::DamnedIntro {
|
||||||
|
character_id: identities(1).into_iter().next().unwrap(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn result_class(result: &ActionResult) -> Option<&'static str> {
|
||||||
|
match result.title() {
|
||||||
|
ActionResultTitle::Drunk | ActionResultTitle::RoleBlocked => Some("drunk"),
|
||||||
|
ActionResultTitle::PowerSeer
|
||||||
|
| ActionResultTitle::Adjudicator
|
||||||
|
| ActionResultTitle::Arcanist
|
||||||
|
| ActionResultTitle::GraveDigger
|
||||||
|
| ActionResultTitle::Mortician
|
||||||
|
| ActionResultTitle::Insomniac
|
||||||
|
| ActionResultTitle::Empath
|
||||||
|
| ActionResultTitle::BeholderSawNothing
|
||||||
|
| ActionResultTitle::BeholderSawEverything
|
||||||
|
| ActionResultTitle::Seer => Some("intel"),
|
||||||
|
ActionResultTitle::ShiftFailed => Some("wolves"),
|
||||||
|
ActionResultTitle::GoBackToSleep
|
||||||
|
| ActionResultTitle::Continue
|
||||||
|
| ActionResultTitle::SkippedByHost => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_class(prompt: &ActionPrompt) -> Option<&'static str> {
|
||||||
|
match prompt {
|
||||||
|
ActionPrompt::ElderReveal { .. }
|
||||||
|
| ActionPrompt::RoleChange { .. }
|
||||||
|
| ActionPrompt::CoverOfDarkness => None,
|
||||||
|
ActionPrompt::BeholderWakes { .. }
|
||||||
|
| ActionPrompt::Seer { .. }
|
||||||
|
| ActionPrompt::Arcanist { .. }
|
||||||
|
| ActionPrompt::Gravedigger { .. }
|
||||||
|
| ActionPrompt::Adjudicator { .. }
|
||||||
|
| ActionPrompt::PowerSeer { .. }
|
||||||
|
| ActionPrompt::Mortician { .. }
|
||||||
|
| ActionPrompt::BeholderChooses { .. }
|
||||||
|
| ActionPrompt::MasonsWake { .. }
|
||||||
|
| ActionPrompt::MasonLeaderRecruit { .. }
|
||||||
|
| ActionPrompt::Empath { .. } => Some("intel"),
|
||||||
|
ActionPrompt::Protector { .. }
|
||||||
|
| ActionPrompt::Guardian { .. }
|
||||||
|
| ActionPrompt::Vindicator { .. } => Some("defensive"),
|
||||||
|
ActionPrompt::Hunter { .. }
|
||||||
|
| ActionPrompt::Militia { .. }
|
||||||
|
| ActionPrompt::MapleWolf { .. }
|
||||||
|
| ActionPrompt::PyreMaster { .. } => Some("offensive"),
|
||||||
|
ActionPrompt::WolvesIntro { .. }
|
||||||
|
| ActionPrompt::WolfPackKill { .. }
|
||||||
|
| ActionPrompt::Shapeshifter { .. }
|
||||||
|
| ActionPrompt::AlphaWolf { .. }
|
||||||
|
| ActionPrompt::DireWolf { .. }
|
||||||
|
| ActionPrompt::LoneWolfKill { .. }
|
||||||
|
| ActionPrompt::Insomniac { .. }
|
||||||
|
| ActionPrompt::Bloodletter { .. } => Some("wolves"),
|
||||||
|
ActionPrompt::DamnedIntro { .. } => Some("damned"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,378 @@
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use gloo::dialogs::prompt;
|
||||||
|
// Copyright (C) 2025-2026 Emilis Bliūdžius
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
use leptos::{
|
||||||
|
ev::{Event, Targeted, WheelEvent},
|
||||||
|
prelude::*,
|
||||||
|
web_sys::HtmlSelectElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
use core::num::NonZeroU8;
|
||||||
|
|
||||||
|
use werewolves_proto::{
|
||||||
|
message::{host::ServerToHostMessage, night::ActionPrompt},
|
||||||
|
role::{PreviousGuardianAction, RoleTitle},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::app::{
|
||||||
|
components::IncDecU8,
|
||||||
|
pages::{game::host::overrides, night_actions},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn PromptScreenTest(
|
||||||
|
prompt: RwSignal<Option<ActionPrompt>>,
|
||||||
|
page: RwSignal<usize>,
|
||||||
|
send: WriteSignal<Option<ServerToHostMessage>>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let options = move || {
|
||||||
|
let prompt_signal = prompt;
|
||||||
|
let Some(prompt) = prompt.get() else {
|
||||||
|
return ().into_any();
|
||||||
|
};
|
||||||
|
match prompt {
|
||||||
|
ActionPrompt::WolvesIntro { wolves } => {
|
||||||
|
let new_count = RwSignal::new(wolves.len() as u8);
|
||||||
|
|
||||||
|
Effect::new(move || {
|
||||||
|
let new_prompt = ActionPrompt::WolvesIntro {
|
||||||
|
wolves: overrides::identities(new_count.get() as _)
|
||||||
|
.into_iter()
|
||||||
|
.zip(RoleTitle::ALL.into_iter().filter(|w| w.wolf()).cycle())
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(
|
||||||
|
new_prompt.clone(),
|
||||||
|
0,
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
view! {
|
||||||
|
<span>"wolf count"</span>
|
||||||
|
<IncDecU8 value=new_count value_range=1..=0xFF />
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionPrompt::RoleChange { new_role, .. } => {
|
||||||
|
let roles = RoleTitle::ALL
|
||||||
|
.into_iter()
|
||||||
|
.map(|role| {
|
||||||
|
view! { <option selected=role == new_role>{role.to_string().to_case(Case::Title)}</option> }
|
||||||
|
})
|
||||||
|
.collect_view();
|
||||||
|
let on_change_cb = move |t: Targeted<Event, HtmlSelectElement>| {
|
||||||
|
let select = t.target();
|
||||||
|
let selected = select.selected_index();
|
||||||
|
if selected == -1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(new_role) = RoleTitle::ALL.into_iter().nth(selected as _) {
|
||||||
|
let new_prompt = ActionPrompt::RoleChange {
|
||||||
|
character_id: super::identity(),
|
||||||
|
new_role,
|
||||||
|
};
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(
|
||||||
|
new_prompt.clone(),
|
||||||
|
0,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let on_wheel = move |t: Targeted<WheelEvent, HtmlSelectElement>| {
|
||||||
|
let target = t.target();
|
||||||
|
let index = target.selected_index();
|
||||||
|
let new_index = match t.delta_y().total_cmp(&0.0) {
|
||||||
|
core::cmp::Ordering::Equal => return,
|
||||||
|
core::cmp::Ordering::Less => {
|
||||||
|
if index != 0 {
|
||||||
|
index - 1
|
||||||
|
} else {
|
||||||
|
(target.children().length() - 1) as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
core::cmp::Ordering::Greater => {
|
||||||
|
if index + 1 < target.children().length() as i32 {
|
||||||
|
index + 1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
target.set_selected_index(new_index);
|
||||||
|
if let Some(new_role) = RoleTitle::ALL.into_iter().nth(new_index as _) {
|
||||||
|
let new_prompt = ActionPrompt::RoleChange {
|
||||||
|
character_id: super::identity(),
|
||||||
|
new_role,
|
||||||
|
};
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(
|
||||||
|
new_prompt.clone(),
|
||||||
|
0,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="prompt-option">
|
||||||
|
<span>{"new role"}</span>
|
||||||
|
<select on:wheel:target=on_wheel on:change:target=on_change_cb>
|
||||||
|
{roles}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionPrompt::Hunter { current_target, .. } => {
|
||||||
|
let toggle_target = current_target.is_none().then_some(super::identity());
|
||||||
|
let button_text = if toggle_target.is_some() {
|
||||||
|
"remove previous target"
|
||||||
|
} else {
|
||||||
|
"set previous target"
|
||||||
|
};
|
||||||
|
let on_toggle = move |_| {
|
||||||
|
let new_prompt = ActionPrompt::Hunter {
|
||||||
|
character_id: super::identity(),
|
||||||
|
current_target: toggle_target.clone(),
|
||||||
|
living_players: overrides::identities(20),
|
||||||
|
marked: None,
|
||||||
|
};
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(
|
||||||
|
new_prompt.clone(),
|
||||||
|
0,
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="prompt-options">
|
||||||
|
<button on:click=on_toggle>{button_text}</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionPrompt::MapleWolf {
|
||||||
|
nights_til_starvation,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
let nights_til_starvation = RwSignal::new(nights_til_starvation);
|
||||||
|
Effect::new(move || {
|
||||||
|
let new_prompt = ActionPrompt::MapleWolf {
|
||||||
|
character_id: super::identity(),
|
||||||
|
nights_til_starvation: nights_til_starvation.get(),
|
||||||
|
living_players: overrides::identities(20),
|
||||||
|
marked: None,
|
||||||
|
};
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(
|
||||||
|
new_prompt.clone(),
|
||||||
|
0,
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="prompt-options">
|
||||||
|
<span>"nights til starvation"</span>
|
||||||
|
<IncDecU8 value=nights_til_starvation />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionPrompt::Guardian { .. } => {
|
||||||
|
let none_disabled = move || {
|
||||||
|
matches!(
|
||||||
|
prompt_signal.get(),
|
||||||
|
Some(ActionPrompt::Guardian { previous: None, .. })
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let prev_none = move |_| {
|
||||||
|
let new_prompt = ActionPrompt::Guardian {
|
||||||
|
character_id: super::identity(),
|
||||||
|
previous: None,
|
||||||
|
living_players: super::identities(20),
|
||||||
|
marked: None,
|
||||||
|
};
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(
|
||||||
|
new_prompt.clone(),
|
||||||
|
0,
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let p = prompt.clone();
|
||||||
|
let prot_disabled = move || {
|
||||||
|
matches!(
|
||||||
|
p,
|
||||||
|
ActionPrompt::Guardian {
|
||||||
|
previous: Some(PreviousGuardianAction::Protect(_)),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let prev_prot = move |_| {
|
||||||
|
let new_prompt = ActionPrompt::Guardian {
|
||||||
|
character_id: super::identity(),
|
||||||
|
previous: Some(PreviousGuardianAction::Protect(
|
||||||
|
super::identities(2).into_iter().nth(1).unwrap(),
|
||||||
|
)),
|
||||||
|
living_players: super::identities(20),
|
||||||
|
marked: None,
|
||||||
|
};
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(
|
||||||
|
new_prompt.clone(),
|
||||||
|
0,
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
let p = prompt.clone();
|
||||||
|
let guard_disabled = move || {
|
||||||
|
matches!(
|
||||||
|
p,
|
||||||
|
ActionPrompt::Guardian {
|
||||||
|
previous: Some(PreviousGuardianAction::Guard(_)),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let prev_guard = move |_| {
|
||||||
|
let new_prompt = ActionPrompt::Guardian {
|
||||||
|
character_id: super::identity(),
|
||||||
|
previous: Some(PreviousGuardianAction::Guard(
|
||||||
|
super::identities(2).into_iter().nth(1).unwrap(),
|
||||||
|
)),
|
||||||
|
living_players: super::identities(20),
|
||||||
|
marked: None,
|
||||||
|
};
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(
|
||||||
|
new_prompt.clone(),
|
||||||
|
0,
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<span>"previous protect"</span>
|
||||||
|
<div class="option-set">
|
||||||
|
<button disabled=none_disabled on:click=prev_none>
|
||||||
|
"none"
|
||||||
|
</button>
|
||||||
|
<button disabled=prot_disabled on:click=prev_prot>
|
||||||
|
"protected"
|
||||||
|
</button>
|
||||||
|
<button disabled=guard_disabled on:click=prev_guard>
|
||||||
|
"guarded"
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionPrompt::MasonsWake { masons, .. } => {
|
||||||
|
let mason_count = RwSignal::new(masons.len() as u8);
|
||||||
|
Effect::new(move || {
|
||||||
|
let new_prompt = ActionPrompt::MasonsWake {
|
||||||
|
leader: super::identity(),
|
||||||
|
masons: super::identities(mason_count.get() as _),
|
||||||
|
};
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(
|
||||||
|
new_prompt.clone(),
|
||||||
|
0,
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
view! {
|
||||||
|
<span>"masons"</span>
|
||||||
|
<IncDecU8 value=mason_count value_range=1..=0xFF />
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionPrompt::MasonLeaderRecruit { recruits_left, .. } => {
|
||||||
|
let recruits_left = RwSignal::new(recruits_left.get());
|
||||||
|
Effect::new(move || {
|
||||||
|
let new_prompt = ActionPrompt::MasonLeaderRecruit {
|
||||||
|
character_id: super::identity(),
|
||||||
|
recruits_left: NonZeroU8::new(recruits_left.get()).unwrap(),
|
||||||
|
potential_recruits: overrides::identities(20),
|
||||||
|
marked: None,
|
||||||
|
};
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(
|
||||||
|
new_prompt.clone(),
|
||||||
|
0,
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
view! { <IncDecU8 value=recruits_left value_range=1..=0xFF /> }.into_any()
|
||||||
|
}
|
||||||
|
|
||||||
|
ActionPrompt::BeholderWakes { .. }
|
||||||
|
| ActionPrompt::Protector { .. }
|
||||||
|
| ActionPrompt::Arcanist { .. }
|
||||||
|
| ActionPrompt::Gravedigger { .. }
|
||||||
|
| ActionPrompt::Militia { .. }
|
||||||
|
| ActionPrompt::Adjudicator { .. }
|
||||||
|
| ActionPrompt::PowerSeer { .. }
|
||||||
|
| ActionPrompt::Mortician { .. }
|
||||||
|
| ActionPrompt::BeholderChooses { .. }
|
||||||
|
| ActionPrompt::Empath { .. }
|
||||||
|
| ActionPrompt::Vindicator { .. }
|
||||||
|
| ActionPrompt::PyreMaster { .. }
|
||||||
|
| ActionPrompt::WolfPackKill { .. }
|
||||||
|
| ActionPrompt::AlphaWolf { .. }
|
||||||
|
| ActionPrompt::DireWolf { .. }
|
||||||
|
| ActionPrompt::LoneWolfKill { .. }
|
||||||
|
| ActionPrompt::Bloodletter { .. }
|
||||||
|
| ActionPrompt::Seer { .. }
|
||||||
|
| ActionPrompt::ElderReveal { .. }
|
||||||
|
| ActionPrompt::DamnedIntro { .. }
|
||||||
|
| ActionPrompt::Insomniac { .. }
|
||||||
|
| ActionPrompt::Shapeshifter { .. }
|
||||||
|
| ActionPrompt::CoverOfDarkness => ().into_any(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let prev_page_disabled = move || page.get() == 0;
|
||||||
|
let prev_page = move |_| {
|
||||||
|
if let Some(prompt) = prompt.get() {
|
||||||
|
let new_page = page.get().saturating_sub(1);
|
||||||
|
page.set(new_page);
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(prompt, new_page)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let next_page = move |_| {
|
||||||
|
if let Some(prompt) = prompt.get() {
|
||||||
|
let new_page = page.get().saturating_add(1);
|
||||||
|
page.set(new_page);
|
||||||
|
send.set(Some(ServerToHostMessage::ActionPrompt(prompt, new_page)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let title = move || {
|
||||||
|
prompt
|
||||||
|
.get()
|
||||||
|
.map(|p| p.title().to_string())
|
||||||
|
.unwrap_or_default()
|
||||||
|
};
|
||||||
|
let is_at_max_page = move || {
|
||||||
|
let Some(prompt) = prompt.get() else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
night_actions::pages_for_prompt(prompt, None).len() <= page.get().saturating_add(1)
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="prompt-overrides">
|
||||||
|
<span>{title}</span>
|
||||||
|
<div class="prompt-number">
|
||||||
|
<span>"page:"</span>
|
||||||
|
<button on:click=prev_page disabled=prev_page_disabled>
|
||||||
|
"-"
|
||||||
|
</button>
|
||||||
|
<span>{move || page.get().saturating_add(1)}</span>
|
||||||
|
<button on:click=next_page disabled=is_at_max_page>
|
||||||
|
"+"
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="prompt-options">{options}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,312 @@
|
||||||
|
// Copyright (C) 2025-2026 Emilis Bliūdžius
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
use leptos::{
|
||||||
|
ev::{Event, Targeted, WheelEvent},
|
||||||
|
prelude::*,
|
||||||
|
web_sys::HtmlSelectElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use werewolves_proto::{
|
||||||
|
diedto::DiedToTitle,
|
||||||
|
message::{
|
||||||
|
host::ServerToHostMessage,
|
||||||
|
night::{ActionResult, Visits},
|
||||||
|
},
|
||||||
|
role::{Alignment, RoleTitle},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::app::{components::IncDecU8, pages::game::host::overrides};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ResultScreenTest(
|
||||||
|
result: ActionResult,
|
||||||
|
send: WriteSignal<Option<ServerToHostMessage>>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let options = match result.clone() {
|
||||||
|
ActionResult::BeholderSawNothing
|
||||||
|
| ActionResult::BeholderSawEverything
|
||||||
|
| ActionResult::GoBackToSleep
|
||||||
|
| ActionResult::ShiftFailed
|
||||||
|
| ActionResult::Continue
|
||||||
|
| ActionResult::Drunk
|
||||||
|
| ActionResult::RoleBlocked
|
||||||
|
| ActionResult::SkippedByHost => ().into_any(),
|
||||||
|
ActionResult::Seer(target, alignment) => {
|
||||||
|
let all = Alignment::ALL
|
||||||
|
.into_iter()
|
||||||
|
.map(|align| {
|
||||||
|
let target = target.clone();
|
||||||
|
let on_click = move |_| {
|
||||||
|
send.set(Some(ServerToHostMessage::ActionResult(
|
||||||
|
None,
|
||||||
|
ActionResult::Seer(target.clone(), align),
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<button on:click=on_click disabled=align == alignment>
|
||||||
|
{align.to_string()}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_view();
|
||||||
|
view! {
|
||||||
|
<div class="prompt-options">
|
||||||
|
<div class="option-set">{all}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionResult::PowerSeer { target, powerful } => {
|
||||||
|
let on_toggle = move |_| {
|
||||||
|
send.set(Some(ServerToHostMessage::ActionResult(
|
||||||
|
None,
|
||||||
|
ActionResult::PowerSeer {
|
||||||
|
target: target.clone(),
|
||||||
|
powerful: !powerful,
|
||||||
|
},
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
let text = if powerful.powerful() {
|
||||||
|
"make not powerful"
|
||||||
|
} else {
|
||||||
|
"make powerful"
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="prompt-options">
|
||||||
|
<button on:click=on_toggle>{text}</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionResult::Adjudicator { target, killer } => {
|
||||||
|
let on_toggle = move |_| {
|
||||||
|
send.set(Some(ServerToHostMessage::ActionResult(
|
||||||
|
None,
|
||||||
|
ActionResult::Adjudicator {
|
||||||
|
target: target.clone(),
|
||||||
|
killer: !killer,
|
||||||
|
},
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
let text = if killer.killer() {
|
||||||
|
"make not killer"
|
||||||
|
} else {
|
||||||
|
"make killer"
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="prompt-options">
|
||||||
|
<button on:click=on_toggle>{text}</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionResult::Arcanist(targets, alignment_eq) => {
|
||||||
|
let on_toggle = move |_| {
|
||||||
|
send.set(Some(ServerToHostMessage::ActionResult(
|
||||||
|
None,
|
||||||
|
ActionResult::Arcanist(targets.clone(), !alignment_eq),
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
let text = if alignment_eq.same() {
|
||||||
|
"make different"
|
||||||
|
} else {
|
||||||
|
"make same"
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="prompt-options">
|
||||||
|
<button on:click=on_toggle>{text}</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionResult::GraveDigger(target, role_title) => {
|
||||||
|
let possibilities = [None]
|
||||||
|
.into_iter()
|
||||||
|
.chain(RoleTitle::ALL.into_iter().map(Some))
|
||||||
|
.collect::<Rc<[_]>>();
|
||||||
|
let roles = possibilities
|
||||||
|
.iter()
|
||||||
|
.map(|role| {
|
||||||
|
let text = role
|
||||||
|
.as_ref()
|
||||||
|
.map(|r| r.to_string())
|
||||||
|
.unwrap_or(String::from("empty grave"));
|
||||||
|
view! { <option selected=*role == role_title>{text}</option> }
|
||||||
|
})
|
||||||
|
.collect_view();
|
||||||
|
|
||||||
|
let res_target = target.clone();
|
||||||
|
let p = possibilities.clone();
|
||||||
|
let on_change_cb = move |ev: Targeted<Event, HtmlSelectElement>| {
|
||||||
|
let select = ev.target();
|
||||||
|
let selected = select.selected_index();
|
||||||
|
if selected == -1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(new_role) = p.get(selected as usize) {
|
||||||
|
send.set(Some(ServerToHostMessage::ActionResult(
|
||||||
|
None,
|
||||||
|
ActionResult::GraveDigger(res_target.clone(), *new_role),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let res_target = target.clone();
|
||||||
|
let on_wheel = move |ev: Targeted<WheelEvent, HtmlSelectElement>| {
|
||||||
|
let target = ev.target();
|
||||||
|
let index = target.selected_index();
|
||||||
|
let new_index = match ev.delta_y().total_cmp(&0.0) {
|
||||||
|
core::cmp::Ordering::Equal => return,
|
||||||
|
core::cmp::Ordering::Less => {
|
||||||
|
if index != 0 {
|
||||||
|
index - 1
|
||||||
|
} else {
|
||||||
|
(target.children().length() - 1) as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
core::cmp::Ordering::Greater => {
|
||||||
|
if index + 1 < target.children().length() as i32 {
|
||||||
|
index + 1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
target.set_selected_index(new_index);
|
||||||
|
if let Some(new_role) = possibilities.get(new_index as usize) {
|
||||||
|
send.set(Some(ServerToHostMessage::ActionResult(
|
||||||
|
None,
|
||||||
|
ActionResult::GraveDigger(res_target.clone(), *new_role),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="result-option">
|
||||||
|
<label>{"dug up"}</label>
|
||||||
|
<select on:wheel:target=on_wheel on:change:target=on_change_cb>
|
||||||
|
{roles}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionResult::Mortician(target, died_to_title) => {
|
||||||
|
let roles = DiedToTitle::ALL
|
||||||
|
.into_iter()
|
||||||
|
.map(|died_to| {
|
||||||
|
view! { <option selected=died_to == died_to_title>{died_to.to_string()}</option> }
|
||||||
|
})
|
||||||
|
.collect_view();
|
||||||
|
let res_target = target.clone();
|
||||||
|
let on_change_cb = move |ev: Targeted<Event, HtmlSelectElement>| {
|
||||||
|
let select = ev.target();
|
||||||
|
let selected = select.selected_index();
|
||||||
|
if selected == -1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(died_to) = DiedToTitle::ALL.into_iter().nth(selected as _) {
|
||||||
|
send.set(Some(ServerToHostMessage::ActionResult(
|
||||||
|
None,
|
||||||
|
ActionResult::Mortician(res_target.clone(), died_to),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let res_target = target.clone();
|
||||||
|
let on_wheel = move |ev: Targeted<WheelEvent, HtmlSelectElement>| {
|
||||||
|
let target = ev.target();
|
||||||
|
let index = target.selected_index();
|
||||||
|
let new_index = match ev.delta_y().total_cmp(&0.0) {
|
||||||
|
core::cmp::Ordering::Equal => return,
|
||||||
|
core::cmp::Ordering::Less => {
|
||||||
|
if index != 0 {
|
||||||
|
index - 1
|
||||||
|
} else {
|
||||||
|
(target.children().length() - 1) as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
core::cmp::Ordering::Greater => {
|
||||||
|
if index + 1 < target.children().length() as i32 {
|
||||||
|
index + 1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
target.set_selected_index(new_index);
|
||||||
|
if let Some(died_to) = DiedToTitle::ALL.into_iter().nth(new_index as _) {
|
||||||
|
send.set(Some(ServerToHostMessage::ActionResult(
|
||||||
|
None,
|
||||||
|
ActionResult::Mortician(res_target.clone(), died_to),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="prompt-option">
|
||||||
|
<label>{"died to"}</label>
|
||||||
|
<select on:wheel:target=on_wheel on:change:target=on_change_cb>
|
||||||
|
{roles}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionResult::Insomniac(visits) => {
|
||||||
|
let visits = RwSignal::new(visits.len() as u8);
|
||||||
|
Effect::new(move || {
|
||||||
|
send.set(Some(ServerToHostMessage::ActionResult(
|
||||||
|
None,
|
||||||
|
ActionResult::Insomniac(Visits::new(overrides::identities(visits.get() as _))),
|
||||||
|
)))
|
||||||
|
});
|
||||||
|
view! {
|
||||||
|
<div class="result-number">
|
||||||
|
<span>"visits"</span>
|
||||||
|
<IncDecU8 value=visits value_range=1..=0xFF />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
ActionResult::Empath { target, scapegoat } => {
|
||||||
|
let on_toggle = move |_| {
|
||||||
|
send.set(Some(ServerToHostMessage::ActionResult(
|
||||||
|
None,
|
||||||
|
ActionResult::Empath {
|
||||||
|
scapegoat: !scapegoat,
|
||||||
|
target: target.clone(),
|
||||||
|
},
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
let text = if scapegoat {
|
||||||
|
"make not scapegoat"
|
||||||
|
} else {
|
||||||
|
"make scapegoat"
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="prompt-options">
|
||||||
|
<button on:click=on_toggle>{text}</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div class="result-overrides">
|
||||||
|
<span>{result.title().to_string()}</span>
|
||||||
|
<div class="result-options">{options}</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
.into_any()
|
||||||
|
}
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
use leptos::prelude::*;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn HostLobby() -> impl IntoView {}
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use werewolves_proto::{
|
use werewolves_proto::{
|
||||||
character::CharacterId,
|
character::CharacterId,
|
||||||
|
|
@ -6,12 +7,12 @@ use werewolves_proto::{
|
||||||
host::HostNightMessage,
|
host::HostNightMessage,
|
||||||
night::{ActionPrompt, ActionResponse},
|
night::{ActionPrompt, ActionResponse},
|
||||||
},
|
},
|
||||||
role::PreviousGuardianAction,
|
role::{PreviousGuardianAction, RoleTitle},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
class::AsClasses,
|
class::AsClasses,
|
||||||
components::{Cover, IdentityInline},
|
components::{Cover, IdentityInline, Sample, TutorialBox},
|
||||||
error::WolfError,
|
error::WolfError,
|
||||||
pages::night_actions::{
|
pages::night_actions::{
|
||||||
DamnedIntroPage1, DamnedIntroPage2, RoleChange, WolfPackKill, WolvesIntro,
|
DamnedIntroPage1, DamnedIntroPage2, RoleChange, WolfPackKill, WolvesIntro,
|
||||||
|
|
@ -22,7 +23,7 @@ use crate::app::{
|
||||||
GuardianPagePreviousProtect2, HunterPage1, InsomniacPage1, LoneWolfPage1,
|
GuardianPagePreviousProtect2, HunterPage1, InsomniacPage1, LoneWolfPage1,
|
||||||
MapleWolfPage1, MasonRecruitPage1, MasonRecruitPage2, MasonsWake, MilitiaPage1,
|
MapleWolfPage1, MasonRecruitPage1, MasonRecruitPage2, MasonsWake, MilitiaPage1,
|
||||||
MorticianPage1, PowerSeerPage1, ProtectorPage1, PyremasterPage1, SeerPage1,
|
MorticianPage1, PowerSeerPage1, ProtectorPage1, PyremasterPage1, SeerPage1,
|
||||||
VindicatorPage1,
|
ShapeshifterPage1, VindicatorPage1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -31,27 +32,23 @@ pub trait RolePage {
|
||||||
fn role_pages(&self, big_screen: bool) -> ViewFn;
|
fn role_pages(&self, big_screen: bool) -> ViewFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
pub fn target_picker_tutorials(prompt: &ActionPrompt) -> Vec<AnyView> {
|
||||||
pub fn RolePrompt(
|
#[allow(clippy::match_single_binding)]
|
||||||
|
match prompt {
|
||||||
|
_ => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pages_for_prompt(
|
||||||
prompt: ActionPrompt,
|
prompt: ActionPrompt,
|
||||||
page: usize,
|
reply: Option<WriteSignal<Option<HostNightMessage>>>,
|
||||||
#[prop(optional)] reply: Option<WriteSignal<Option<HostNightMessage>>>,
|
) -> Vec<AnyView> {
|
||||||
#[prop(optional)] error: Option<WriteSignal<Option<WolfError>>>,
|
|
||||||
) -> impl IntoView {
|
|
||||||
let ident = move |character_id: CharacterIdentity| {
|
let ident = move |character_id: CharacterIdentity| {
|
||||||
reply.map(|_| {
|
reply.map(|_| {
|
||||||
view! { <IdentityInline ident=character_id.into() /> }
|
view! { <IdentityInline ident=character_id.into() /> }
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let interactive = prompt.interactive();
|
match prompt {
|
||||||
let targets = prompt.targets().map(|t| t.to_vec().into_boxed_slice());
|
|
||||||
let marked = prompt
|
|
||||||
.marked()
|
|
||||||
.map(|t| [t.0].into_iter().chain(t.1))
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.collect::<Box<_>>();
|
|
||||||
let mut pages: Vec<AnyView> = match prompt {
|
|
||||||
ActionPrompt::CoverOfDarkness => vec![match reply {
|
ActionPrompt::CoverOfDarkness => vec![match reply {
|
||||||
Some(reply) => view! { <Cover reply=reply /> }.into_any(),
|
Some(reply) => view! { <Cover reply=reply /> }.into_any(),
|
||||||
None => view! { <Cover /> }.into_any(),
|
None => view! { <Cover /> }.into_any(),
|
||||||
|
|
@ -180,8 +177,8 @@ pub fn RolePrompt(
|
||||||
ActionPrompt::PyreMaster { character_id, .. } => {
|
ActionPrompt::PyreMaster { character_id, .. } => {
|
||||||
vec![view! { {ident(character_id)} <PyremasterPage1 /> }.into_any()]
|
vec![view! { {ident(character_id)} <PyremasterPage1 /> }.into_any()]
|
||||||
}
|
}
|
||||||
ActionPrompt::Shapeshifter { .. } => {
|
ActionPrompt::Shapeshifter { character_id } => {
|
||||||
vec![]
|
vec![view! {{ident(character_id)} <ShapeshifterPage1 />}.into_any()]
|
||||||
}
|
}
|
||||||
ActionPrompt::AlphaWolf { character_id, .. } => {
|
ActionPrompt::AlphaWolf { character_id, .. } => {
|
||||||
vec![view! { {ident(character_id)} <AlphaWolfPage1 /> }.into_any()]
|
vec![view! { {ident(character_id)} <AlphaWolfPage1 /> }.into_any()]
|
||||||
|
|
@ -224,7 +221,27 @@ pub fn RolePrompt(
|
||||||
ActionPrompt::WolvesIntro { wolves } => {
|
ActionPrompt::WolvesIntro { wolves } => {
|
||||||
vec![view! { <WolvesIntro wolves=wolves /> }.into_any()]
|
vec![view! { <WolvesIntro wolves=wolves /> }.into_any()]
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn RolePrompt(
|
||||||
|
prompt: ActionPrompt,
|
||||||
|
page: usize,
|
||||||
|
#[prop(optional)] reply: Option<WriteSignal<Option<HostNightMessage>>>,
|
||||||
|
#[prop(optional)] error: Option<WriteSignal<Option<WolfError>>>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
let interactive = prompt.interactive();
|
||||||
|
let targets = prompt.targets().map(|t| t.to_vec().into_boxed_slice());
|
||||||
|
let marked = prompt
|
||||||
|
.marked()
|
||||||
|
.map(|t| [t.0].into_iter().chain(t.1))
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect::<Box<_>>();
|
||||||
|
let tutorials = target_picker_tutorials(&prompt);
|
||||||
|
let shapeshifter = matches!(prompt, ActionPrompt::Shapeshifter { .. });
|
||||||
|
let mut pages: Vec<AnyView> = pages_for_prompt(prompt, reply);
|
||||||
// isn't it great that AnyView isn't Clone????
|
// isn't it great that AnyView isn't Clone????
|
||||||
if let Some(page) = pages.get(page).is_some().then(|| {
|
if let Some(page) = pages.get(page).is_some().then(|| {
|
||||||
let p = pages.swap_remove(page);
|
let p = pages.swap_remove(page);
|
||||||
|
|
@ -249,6 +266,23 @@ pub fn RolePrompt(
|
||||||
}) {
|
}) {
|
||||||
return page.into_any();
|
return page.into_any();
|
||||||
}
|
}
|
||||||
|
// shapeshifter gets a yes/no
|
||||||
|
if shapeshifter {
|
||||||
|
let decision = RwSignal::new(None);
|
||||||
|
if let Some(reply) = reply {
|
||||||
|
Effect::new(move || match decision.get() {
|
||||||
|
Some(true) => reply.set(Some(HostNightMessage::ActionResponse(
|
||||||
|
ActionResponse::Shapeshift,
|
||||||
|
))),
|
||||||
|
Some(false) => reply.set(Some(HostNightMessage::ActionResponse(
|
||||||
|
ActionResponse::Continue,
|
||||||
|
))),
|
||||||
|
None => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return view! { <BooleanPicker decision=decision.write_only() /> }.into_any();
|
||||||
|
}
|
||||||
let target_picker = match targets {
|
let target_picker = match targets {
|
||||||
Some(targets) => {
|
Some(targets) => {
|
||||||
let pick = RwSignal::new(None);
|
let pick = RwSignal::new(None);
|
||||||
|
|
@ -288,8 +322,11 @@ pub fn RolePrompt(
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let tutorials = (continue_btn.is_some() && !tutorials.is_empty())
|
||||||
|
.then(|| tutorials.into_iter().collect_view());
|
||||||
view! {
|
view! {
|
||||||
{target_picker}
|
{target_picker}
|
||||||
|
{tutorials}
|
||||||
{continue_btn}
|
{continue_btn}
|
||||||
}
|
}
|
||||||
.into_any()
|
.into_any()
|
||||||
|
|
@ -323,5 +360,19 @@ pub fn TargetPicker(
|
||||||
})
|
})
|
||||||
.collect_view();
|
.collect_view();
|
||||||
|
|
||||||
view! { <div class="target-picker">{targets}</div> }
|
view! { <div class="target-picker" class:allow-scroll=pick.is_some()>{targets}</div> }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn BooleanPicker(decision: WriteSignal<Option<bool>>) -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div class="bool-picker">
|
||||||
|
<button class="no-hover" on:click=move |_| decision.set(Some(true))>
|
||||||
|
"yes"
|
||||||
|
</button>
|
||||||
|
<button class="no-hover" on:click=move |_| decision.set(Some(false))>
|
||||||
|
"no"
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,11 @@ use werewolves_proto::message::{
|
||||||
use crate::app::{
|
use crate::app::{
|
||||||
components::Cover,
|
components::Cover,
|
||||||
pages::night_actions::{
|
pages::night_actions::{
|
||||||
DrunkPage, RoleblockPage,
|
DrunkPage, RoleblockPage, ShiftFailed,
|
||||||
role::{
|
role::{
|
||||||
AdjudicatorResult, ArcanistResult, EmpathResult, GravediggerResultPage,
|
AdjudicatorResult, ArcanistResult, BeholderSawEverything, BeholderSawNothing,
|
||||||
InsomniacResult, MorticianResultPage, PowerSeerResult, SeerResult,
|
EmpathResult, GravediggerResultPage, InsomniacResult, MorticianResultPage,
|
||||||
|
PowerSeerResult, SeerResult,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -23,57 +24,41 @@ pub fn RoleResult(
|
||||||
#[prop(optional)] reply: Option<WriteSignal<Option<HostNightMessage>>>,
|
#[prop(optional)] reply: Option<WriteSignal<Option<HostNightMessage>>>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let body = match result {
|
let body = match result {
|
||||||
ActionResult::RoleBlocked => view!{
|
ActionResult::RoleBlocked => view! { <RoleblockPage /> }.into_any(),
|
||||||
<RoleblockPage />
|
ActionResult::Drunk => view! { <DrunkPage /> }.into_any(),
|
||||||
}.into_any(),
|
ActionResult::Seer(target, alignment) => view! { <SeerResult target=target.into_public() alignment=alignment /> }.into_any(),
|
||||||
ActionResult::Drunk => view! {
|
ActionResult::PowerSeer { target, powerful } => view! { <PowerSeerResult target=target.into_public() powerful=powerful /> }.into_any(),
|
||||||
<DrunkPage />
|
ActionResult::Adjudicator { target, killer } => view! { <AdjudicatorResult target=target.into_public() killer=killer /> }.into_any(),
|
||||||
}.into_any(),
|
|
||||||
ActionResult::Seer(target, alignment) => view!{
|
|
||||||
<SeerResult target=target.into_public() alignment=alignment/>
|
|
||||||
}.into_any(),
|
|
||||||
ActionResult::PowerSeer { target, powerful } => view!{
|
|
||||||
<PowerSeerResult target=target.into_public() powerful=powerful/>
|
|
||||||
}.into_any(),
|
|
||||||
ActionResult::Adjudicator { target, killer } => view!{
|
|
||||||
<AdjudicatorResult target=target.into_public() killer=killer/>
|
|
||||||
}.into_any(),
|
|
||||||
ActionResult::Arcanist((target1, target2), alignment_eq) => view! {
|
ActionResult::Arcanist((target1, target2), alignment_eq) => view! {
|
||||||
<ArcanistResult
|
<ArcanistResult
|
||||||
value=alignment_eq
|
value=alignment_eq
|
||||||
targets=(target1.into_public(), target2.into_public())
|
targets=(target1.into_public(), target2.into_public())
|
||||||
/>
|
/>
|
||||||
}.into_any(),
|
}.into_any(),
|
||||||
ActionResult::GraveDigger(target, role) => view! {
|
ActionResult::GraveDigger(target, role) => view! { <GravediggerResultPage target=target.into_public() role=role /> }.into_any(),
|
||||||
<GravediggerResultPage target=target.into_public() role=role/>
|
ActionResult::Mortician(target, died_to) => view! { <MorticianResultPage target=target.into_public() died_to=died_to /> }.into_any(),
|
||||||
}.into_any(),
|
ActionResult::Insomniac(visits) => view! { <InsomniacResult visits=visits /> }.into_any(),
|
||||||
ActionResult::Mortician(target, died_to) => view!{
|
ActionResult::Empath { target, scapegoat } => view! { <EmpathResult target=target.into_public() scapegoat=scapegoat /> }.into_any(),
|
||||||
<MorticianResultPage target=target.into_public() died_to=died_to />
|
ActionResult::BeholderSawNothing => view! { <BeholderSawNothing /> }.into_any(),
|
||||||
}.into_any(),
|
ActionResult::BeholderSawEverything => view! { <BeholderSawEverything /> }.into_any(),
|
||||||
ActionResult::Insomniac(visits) => view!{
|
|
||||||
<InsomniacResult visits=visits/>
|
|
||||||
}.into_any(),
|
|
||||||
ActionResult::Empath { target, scapegoat } => view! {
|
|
||||||
<EmpathResult target=target.into_public() scapegoat=scapegoat />
|
|
||||||
}.into_any(),
|
|
||||||
ActionResult::BeholderSawNothing => todo!(),
|
|
||||||
ActionResult::BeholderSawEverything => todo!(),
|
|
||||||
ActionResult::GoBackToSleep => return match reply {
|
ActionResult::GoBackToSleep => return match reply {
|
||||||
Some(reply) => view! { <Cover message="go to sleep" reply=reply reply_to_send=HostNightMessage::Next /> }
|
Some(reply) => view! { <Cover message="go to sleep" reply=reply reply_to_send=HostNightMessage::Next /> }
|
||||||
.into_any(),
|
.into_any(),
|
||||||
None => view! { <Cover message="go to sleep" /> }.into_any(),
|
None => view! { <Cover message="go to sleep" /> }.into_any(),
|
||||||
},
|
},
|
||||||
ActionResult::ShiftFailed => todo!(),
|
ActionResult::ShiftFailed => view!{<ShiftFailed />}.into_any(),
|
||||||
|
ActionResult::SkippedByHost |
|
||||||
ActionResult::Continue => {
|
ActionResult::Continue => {
|
||||||
Effect::new(move || {
|
Effect::new(move || {
|
||||||
let Some(reply) = reply else {
|
let Some(reply) = reply else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
reply.set(Some(HostNightMessage::Next));
|
// NOTE: in the old code this was a GetState and the return was
|
||||||
|
// a blank <Cover />
|
||||||
|
reply.set(Some(HostNightMessage::Next));
|
||||||
});
|
});
|
||||||
().into_any()
|
().into_any()
|
||||||
}
|
}
|
||||||
ActionResult::SkippedByHost => todo!(),
|
|
||||||
};
|
};
|
||||||
let next_btn = reply.map(|reply| {
|
let next_btn = reply.map(|reply| {
|
||||||
view! {
|
view! {
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@ pub fn AdjudicatorPage1() -> impl IntoView {
|
||||||
<div class="role-page">
|
<div class="role-page">
|
||||||
<span class="defensive box title">"ADJUDICATOR"</span>
|
<span class="defensive box title">"ADJUDICATOR"</span>
|
||||||
<div class="information box defensive faint">
|
<div class="information box defensive faint">
|
||||||
<h4>"PICK A PLAYER"</h4>
|
<span>"PICK A PLAYER"</span>
|
||||||
<Icon source=IconSource::Killer r#type=IconType::Fit />
|
<Icon source=IconSource::Killer r#type=IconType::Fit />
|
||||||
<h4 class="yellow">"YOU WILL CHECK IF THEY APPEAR AS A KILLER"</h4>
|
<span class="yellow">"YOU WILL CHECK IF THEY APPEAR AS A KILLER"</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +47,7 @@ pub fn AdjudicatorResult(killer: Killer, target: PublicIdentity) -> impl IntoVie
|
||||||
<div class="information box defensive faint">
|
<div class="information box defensive faint">
|
||||||
<IdentityInline ident=target.clone() />
|
<IdentityInline ident=target.clone() />
|
||||||
{icon}
|
{icon}
|
||||||
<h3 class="yellow">{text}</h3>
|
<span class="yellow">{text}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,14 @@ pub fn ArcanistResult(
|
||||||
targets: (PublicIdentity, PublicIdentity),
|
targets: (PublicIdentity, PublicIdentity),
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let text = match value {
|
let text = match value {
|
||||||
AlignmentEq::Same => "ARE THE SAME",
|
AlignmentEq::Same => view! {"ARE THE SAME"}.into_any(),
|
||||||
AlignmentEq::Different => "ARE DIFFERENT",
|
AlignmentEq::Different => {
|
||||||
|
view! {"ARE "<span class="wolves underline">"DIFFERENT"</span>}.into_any()
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let icons = match value {
|
let icons = match value {
|
||||||
AlignmentEq::Same => view! { <Icon source=IconSource::Equal /> },
|
AlignmentEq::Same => IconSource::BalancedScales,
|
||||||
AlignmentEq::Different => view! { <Icon source=IconSource::NotEqual /> },
|
AlignmentEq::Different => IconSource::UnbalancedScales,
|
||||||
};
|
};
|
||||||
view! {
|
view! {
|
||||||
<div class="role-page">
|
<div class="role-page">
|
||||||
|
|
@ -55,7 +57,7 @@ pub fn ArcanistResult(
|
||||||
<span class="and">"AND"</span>
|
<span class="and">"AND"</span>
|
||||||
<IdentityInline ident=targets.1 />
|
<IdentityInline ident=targets.1 />
|
||||||
</div>
|
</div>
|
||||||
<div class="icons">{icons}</div>
|
<Icon source=icons />
|
||||||
<span class="yellow">{text}</span>
|
<span class="yellow">{text}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,9 @@ pub fn BeholderSawNothing() -> impl IntoView {
|
||||||
<div class="role-page">
|
<div class="role-page">
|
||||||
<span class="intel box title">"BEHOLDER"</span>
|
<span class="intel box title">"BEHOLDER"</span>
|
||||||
<div class="information box intel faint">
|
<div class="information box intel faint">
|
||||||
<h1>"YOUR TARGET HAS DIED"</h1>
|
<span>"YOUR TARGET HAS DIED"</span>
|
||||||
<Icon source=IconSource::RedX />
|
<Icon source=IconSource::RedX />
|
||||||
<h1>"BUT SAW NOTHING"</h1>
|
<span>"BUT SAW NOTHING"</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
use crate::app::components::{Icon, IconSource};
|
use crate::app::components::{Icon, IconSource, IconType};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn BloodletterPage1() -> impl IntoView {
|
pub fn BloodletterPage1() -> impl IntoView {
|
||||||
|
|
@ -24,9 +24,13 @@ pub fn BloodletterPage1() -> impl IntoView {
|
||||||
<div class="information box wolves faint">
|
<div class="information box wolves faint">
|
||||||
<span>"PICK A PLAYER"</span>
|
<span>"PICK A PLAYER"</span>
|
||||||
<span class="inline-icons">
|
<span class="inline-icons">
|
||||||
"THEY'LL APPEAR AS A WOLF " <Icon source=IconSource::Wolves /> "KILLER"
|
"THEY'LL APPEAR AS A" <span class="wolves underline">"WOLF"</span>
|
||||||
<Icon source=IconSource::Killer /> "AND POWERFUL"
|
<Icon source=IconSource::Wolves r#type=IconType::Shrink />
|
||||||
<Icon source=IconSource::Powerful /> "IN CHECKS FOR 2 NIGHTS"
|
<span class="wolves underline">"KILLER"</span>
|
||||||
|
<Icon source=IconSource::Killer r#type=IconType::Shrink /> "AND"
|
||||||
|
<span class="wolves underline">"POWERFUL"</span>
|
||||||
|
<Icon source=IconSource::Powerful r#type=IconType::Shrink />
|
||||||
|
"IN CHECKS FOR 2 NIGHTS"
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
use crate::app::components::{Icon, IconSource};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ShapeshifterPage1() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div class="role-page">
|
||||||
|
<span class="wolves box title">"SHAPESHIFTER"</span>
|
||||||
|
<div class="information box wolves faint">
|
||||||
|
<span>
|
||||||
|
"WOULD YOU LIKE TO USE YOUR " <span class="yellow">{"ONCE PER GAME"}</span>
|
||||||
|
" SHAPESHIFT ABILITY?"
|
||||||
|
</span>
|
||||||
|
<span class="breakable">
|
||||||
|
<span class="yellow breakable">"YOU WILL DIE"</span>
|
||||||
|
", BUT THE TARGET OF THE WOLFPACK KILL SHALL INSTEAD BECOME A WOLF"
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ShiftFailed() -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div class="role-page">
|
||||||
|
<span class="wolves box title">"SHIFT FAILED"</span>
|
||||||
|
<div class="information wolves box faint">
|
||||||
|
"YOUR SHIFT HAS FAILED" <Icon source=IconSource::RedX />
|
||||||
|
"YOU RETAIN YOUR SHAPESHIFT ABILITY"
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue