mirror of
https://git.srhtnin.garden/sarah/srhtnin.github.io.git
synced 2026-10-13 18:38:11 +02:00
bleh
This commit is contained in:
1 parent
17c29270cb
commit
b0bc6ecc03
13 files changed
+1809
-168
No files matched your search
@@ -132,17 +132,53 @@ $PageSection = "Admin";
|
||||
>
|
||||
</label>
|
||||
|
||||
<label class="AdminEditorField">
|
||||
<span>Cooldown (ms)</span>
|
||||
<div class="AdminEditorField">
|
||||
<span>Cooldown</span>
|
||||
|
||||
<div class="AdminDurationInputs">
|
||||
<label>
|
||||
<span>Hours</span>
|
||||
<input
|
||||
id="AdminMutationCooldownHours"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
required
|
||||
>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Minutes</span>
|
||||
<input
|
||||
id="AdminMutationCooldownMinutes"
|
||||
type="number"
|
||||
min="0"
|
||||
max="59"
|
||||
step="1"
|
||||
required
|
||||
>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Seconds</span>
|
||||
<input
|
||||
id="AdminMutationCooldownSeconds"
|
||||
type="number"
|
||||
min="0"
|
||||
max="59"
|
||||
step="1"
|
||||
required
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<input
|
||||
id="AdminMutationCooldown"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1000"
|
||||
required
|
||||
type="hidden"
|
||||
>
|
||||
|
||||
<small id="AdminMutationCooldownHint"></small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<label class="AdminEditorField">
|
||||
<span>Rotation</span>
|
||||
|
||||
+131
-5
@@ -80,7 +80,7 @@ $PageSection = "Admin";
|
||||
required
|
||||
>
|
||||
<small>
|
||||
Used by saves, recipes and the sprite folder.
|
||||
Used by saves, recipes and repository sprite fallbacks.
|
||||
</small>
|
||||
</label>
|
||||
|
||||
@@ -118,17 +118,69 @@ $PageSection = "Admin";
|
||||
</small>
|
||||
</label>
|
||||
|
||||
<label class="AdminEditorField">
|
||||
<span>Growth time (ms)</span>
|
||||
<div class="AdminEditorField">
|
||||
<span>Growth time</span>
|
||||
|
||||
<div class="AdminDurationInputs">
|
||||
<label>
|
||||
<span>Hours</span>
|
||||
<input
|
||||
id="AdminPlantGrowthHours"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
required
|
||||
>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Minutes</span>
|
||||
<input
|
||||
id="AdminPlantGrowthMinutes"
|
||||
type="number"
|
||||
min="0"
|
||||
max="59"
|
||||
step="1"
|
||||
required
|
||||
>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>Seconds</span>
|
||||
<input
|
||||
id="AdminPlantGrowthSeconds"
|
||||
type="number"
|
||||
min="0"
|
||||
max="59"
|
||||
step="1"
|
||||
required
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<input
|
||||
id="AdminPlantGrowthTime"
|
||||
name="GrowthTime"
|
||||
type="hidden"
|
||||
>
|
||||
|
||||
<small id="AdminPlantGrowthTimeHint"></small>
|
||||
</div>
|
||||
|
||||
<label class="AdminEditorField">
|
||||
<span>Harvest multiplier</span>
|
||||
<input
|
||||
id="AdminPlantHarvestMultiplier"
|
||||
name="HarvestMultiplier"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1000"
|
||||
max="9999.9999"
|
||||
step="0.0001"
|
||||
required
|
||||
>
|
||||
<small id="AdminPlantGrowthTimeHint"></small>
|
||||
<small>
|
||||
Defaults to 1.5. Reward is seed cost × this multiplier.
|
||||
</small>
|
||||
</label>
|
||||
|
||||
<label class="AdminEditorField">
|
||||
@@ -189,6 +241,78 @@ $PageSection = "Admin";
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="AdminPlantEconomyPreview">
|
||||
<h3>Economy</h3>
|
||||
|
||||
<dl>
|
||||
<div>
|
||||
<dt>Seed cost</dt>
|
||||
<dd id="AdminPlantEconomyCost">Unavailable</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Harvest multiplier</dt>
|
||||
<dd id="AdminPlantEconomyMultiplier">1.5×</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Harvest reward</dt>
|
||||
<dd id="AdminPlantEconomyReward">Unavailable</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Net profit</dt>
|
||||
<dd id="AdminPlantEconomyProfit">Unavailable</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Growth time</dt>
|
||||
<dd id="AdminPlantEconomyGrowth">Unavailable</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>DPH</dt>
|
||||
<dd id="AdminPlantEconomyDph">Unavailable</dd>
|
||||
</div>
|
||||
<div class="AdminPlantEconomySource">
|
||||
<dt>Price source</dt>
|
||||
<dd id="AdminPlantEconomySource">Unavailable</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="AdminPlantImagesSection">
|
||||
<h2>Sprites</h2>
|
||||
|
||||
<p>
|
||||
Optional. You can save a plant with no sprites and add them later.
|
||||
Upload PNG files named by stage, such as
|
||||
<code>1.png</code>, <code>2.png</code>, <code>3.png</code>.
|
||||
</p>
|
||||
|
||||
<div class="AdminPlantImageUpload">
|
||||
<input
|
||||
id="AdminPlantImageFiles"
|
||||
type="file"
|
||||
accept="image/png,.png"
|
||||
multiple
|
||||
>
|
||||
|
||||
<button
|
||||
id="AdminPlantImageUploadButton"
|
||||
class="ActionButton AdminInlineButton"
|
||||
type="button"
|
||||
>
|
||||
Upload selected sprites
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p
|
||||
id="AdminPlantImageNote"
|
||||
class="AdminPlantImageNote"
|
||||
></p>
|
||||
|
||||
<div
|
||||
id="AdminPlantImageList"
|
||||
class="AdminPlantImageList"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div class="AdminEditorActions">
|
||||
@@ -219,7 +343,9 @@ $PageSection = "Admin";
|
||||
|
||||
<script src="/Scripts/Garden/PlantImages.js"></script>
|
||||
<script src="/Scripts/Garden/Plants.js"></script>
|
||||
<script src="/Scripts/Garden/Mutations.js"></script>
|
||||
<script src="/Scripts/Garden/Save.js"></script>
|
||||
<script src="/Scripts/Garden/Economy.js"></script>
|
||||
<script src="/Scripts/Garden/AdminPlantEditor.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -175,12 +175,34 @@ function BindAdminMutationEditor() {
|
||||
RenderAdminMutationJsonPreview
|
||||
);
|
||||
|
||||
document.getElementById(
|
||||
"AdminMutationCooldown"
|
||||
).addEventListener(
|
||||
"input",
|
||||
UpdateAdminMutationCooldownHint
|
||||
);
|
||||
for (
|
||||
const ElementId
|
||||
of [
|
||||
"AdminMutationCooldownHours",
|
||||
"AdminMutationCooldownMinutes",
|
||||
"AdminMutationCooldownSeconds"
|
||||
]
|
||||
) {
|
||||
document.getElementById(
|
||||
ElementId
|
||||
).addEventListener(
|
||||
"input",
|
||||
() => {
|
||||
SyncAdminMutationCooldown();
|
||||
RenderAdminMutationJsonPreview();
|
||||
}
|
||||
);
|
||||
|
||||
document.getElementById(
|
||||
ElementId
|
||||
).addEventListener(
|
||||
"change",
|
||||
() => {
|
||||
SyncAdminMutationCooldown();
|
||||
RenderAdminMutationJsonPreview();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -645,8 +667,7 @@ function LoadAdminMutationIntoForm(
|
||||
) * 100
|
||||
);
|
||||
|
||||
SetAdminMutationField(
|
||||
"AdminMutationCooldown",
|
||||
SetAdminMutationDurationFields(
|
||||
Number(
|
||||
Mutation.Cooldown ?? 0
|
||||
)
|
||||
@@ -769,8 +790,7 @@ function StartNewAdminMutation() {
|
||||
100
|
||||
);
|
||||
|
||||
SetAdminMutationField(
|
||||
"AdminMutationCooldown",
|
||||
SetAdminMutationDurationFields(
|
||||
120000
|
||||
);
|
||||
|
||||
@@ -1403,21 +1423,11 @@ function GetAdminPlantName(
|
||||
function GetAdminMutationPlantImage(
|
||||
PlantKey
|
||||
) {
|
||||
const Images =
|
||||
PlantImages[
|
||||
return GetPlantMatureImageSource(
|
||||
AdminMutationPlantCatalogue[
|
||||
PlantKey
|
||||
] ?? [];
|
||||
|
||||
if (
|
||||
!Array.isArray(Images) ||
|
||||
Images.length === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Images[
|
||||
Images.length - 1
|
||||
];
|
||||
] ?? PlantKey
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2179,6 +2189,101 @@ function RenderAdminMutationJsonPreview() {
|
||||
}
|
||||
|
||||
|
||||
function SetAdminMutationDurationFields(
|
||||
Milliseconds
|
||||
) {
|
||||
Milliseconds = Math.max(
|
||||
0,
|
||||
Number(Milliseconds) || 0
|
||||
);
|
||||
|
||||
const TotalSeconds =
|
||||
Math.round(
|
||||
Milliseconds / 1000
|
||||
);
|
||||
|
||||
SetAdminMutationField(
|
||||
"AdminMutationCooldownHours",
|
||||
Math.floor(
|
||||
TotalSeconds / 3600
|
||||
)
|
||||
);
|
||||
|
||||
SetAdminMutationField(
|
||||
"AdminMutationCooldownMinutes",
|
||||
Math.floor(
|
||||
(TotalSeconds % 3600) / 60
|
||||
)
|
||||
);
|
||||
|
||||
SetAdminMutationField(
|
||||
"AdminMutationCooldownSeconds",
|
||||
TotalSeconds % 60
|
||||
);
|
||||
|
||||
SetAdminMutationField(
|
||||
"AdminMutationCooldown",
|
||||
TotalSeconds * 1000
|
||||
);
|
||||
|
||||
UpdateAdminMutationCooldownHint();
|
||||
}
|
||||
|
||||
|
||||
function SyncAdminMutationCooldown() {
|
||||
const Hours = Number(
|
||||
document.getElementById(
|
||||
"AdminMutationCooldownHours"
|
||||
).value
|
||||
);
|
||||
|
||||
const Minutes = Number(
|
||||
document.getElementById(
|
||||
"AdminMutationCooldownMinutes"
|
||||
).value
|
||||
);
|
||||
|
||||
const Seconds = Number(
|
||||
document.getElementById(
|
||||
"AdminMutationCooldownSeconds"
|
||||
).value
|
||||
);
|
||||
|
||||
const Values = [
|
||||
Hours,
|
||||
Minutes,
|
||||
Seconds
|
||||
];
|
||||
|
||||
if (
|
||||
Values.some(
|
||||
Value =>
|
||||
!Number.isFinite(Value)
|
||||
)
|
||||
) {
|
||||
SetAdminMutationField(
|
||||
"AdminMutationCooldown",
|
||||
""
|
||||
);
|
||||
|
||||
UpdateAdminMutationCooldownHint();
|
||||
return;
|
||||
}
|
||||
|
||||
const Milliseconds =
|
||||
Math.max(0, Hours) * 3600000 +
|
||||
Math.max(0, Minutes) * 60000 +
|
||||
Math.max(0, Seconds) * 1000;
|
||||
|
||||
SetAdminMutationField(
|
||||
"AdminMutationCooldown",
|
||||
Milliseconds
|
||||
);
|
||||
|
||||
UpdateAdminMutationCooldownHint();
|
||||
}
|
||||
|
||||
|
||||
function UpdateAdminMutationCooldownHint() {
|
||||
const Cooldown = Number(
|
||||
document.getElementById(
|
||||
@@ -2190,76 +2295,12 @@ function UpdateAdminMutationCooldownHint() {
|
||||
"AdminMutationCooldownHint"
|
||||
).textContent =
|
||||
Number.isFinite(Cooldown)
|
||||
? FormatAdminMutationDuration(
|
||||
Cooldown
|
||||
)
|
||||
? Cooldown.toLocaleString() +
|
||||
" ms stored"
|
||||
: "";
|
||||
}
|
||||
|
||||
|
||||
function FormatAdminMutationDuration(
|
||||
Milliseconds
|
||||
) {
|
||||
Milliseconds = Math.max(
|
||||
0,
|
||||
Number(Milliseconds)
|
||||
);
|
||||
|
||||
if (Milliseconds < 1000) {
|
||||
return Milliseconds + "ms";
|
||||
}
|
||||
|
||||
const TotalSeconds =
|
||||
Math.round(
|
||||
Milliseconds /
|
||||
1000
|
||||
);
|
||||
|
||||
const Hours =
|
||||
Math.floor(
|
||||
TotalSeconds /
|
||||
3600
|
||||
);
|
||||
|
||||
const Minutes =
|
||||
Math.floor(
|
||||
(
|
||||
TotalSeconds %
|
||||
3600
|
||||
) /
|
||||
60
|
||||
);
|
||||
|
||||
const Seconds =
|
||||
TotalSeconds % 60;
|
||||
|
||||
const Parts = [];
|
||||
|
||||
if (Hours > 0) {
|
||||
Parts.push(
|
||||
Hours + "h"
|
||||
);
|
||||
}
|
||||
|
||||
if (Minutes > 0) {
|
||||
Parts.push(
|
||||
Minutes + "m"
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
Seconds > 0 ||
|
||||
Parts.length === 0
|
||||
) {
|
||||
Parts.push(
|
||||
Seconds + "s"
|
||||
);
|
||||
}
|
||||
|
||||
return Parts.join(" ");
|
||||
}
|
||||
|
||||
|
||||
function SetAdminMutationMessage(
|
||||
Message
|
||||
) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
let AdminPlantCatalogue = {};
|
||||
let AdminPlantMutationCatalogue = {};
|
||||
let AdminPlantEditingKey = null;
|
||||
let AdminPlantSavePending = false;
|
||||
let AdminPlantImagePending = false;
|
||||
|
||||
|
||||
async function StartAdminPlantEditor() {
|
||||
@@ -75,7 +77,7 @@ function BindAdminPlantEditor() {
|
||||
"AdminPlantId",
|
||||
"AdminPlantKey",
|
||||
"AdminPlantName",
|
||||
"AdminPlantGrowthTime",
|
||||
"AdminPlantHarvestMultiplier",
|
||||
"AdminPlantShopPlant",
|
||||
"AdminPlantBaseCost"
|
||||
]
|
||||
@@ -95,11 +97,50 @@ function BindAdminPlantEditor() {
|
||||
);
|
||||
}
|
||||
|
||||
for (
|
||||
const ElementId
|
||||
of [
|
||||
"AdminPlantGrowthHours",
|
||||
"AdminPlantGrowthMinutes",
|
||||
"AdminPlantGrowthSeconds"
|
||||
]
|
||||
) {
|
||||
document.getElementById(
|
||||
ElementId
|
||||
).addEventListener(
|
||||
"input",
|
||||
() => {
|
||||
SyncAdminPlantGrowthTime();
|
||||
RenderAdminPlantPreview();
|
||||
}
|
||||
);
|
||||
|
||||
document.getElementById(
|
||||
ElementId
|
||||
).addEventListener(
|
||||
"change",
|
||||
() => {
|
||||
SyncAdminPlantGrowthTime();
|
||||
RenderAdminPlantPreview();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
document.getElementById(
|
||||
"AdminPlantShopPlant"
|
||||
).addEventListener(
|
||||
"change",
|
||||
UpdateAdminPlantShopFields
|
||||
() => {
|
||||
UpdateAdminPlantShopFields();
|
||||
RenderAdminPlantPreview();
|
||||
}
|
||||
);
|
||||
|
||||
document.getElementById(
|
||||
"AdminPlantImageUploadButton"
|
||||
).addEventListener(
|
||||
"click",
|
||||
UploadAdminPlantImages
|
||||
);
|
||||
}
|
||||
|
||||
@@ -167,6 +208,47 @@ async function AdminPlantRequest(
|
||||
}
|
||||
|
||||
|
||||
async function AdminPlantMutationRequest() {
|
||||
const SaveKey =
|
||||
localStorage.getItem(
|
||||
SaveKeyName
|
||||
);
|
||||
|
||||
const Response = await fetch(
|
||||
ApiUrl +
|
||||
"/AdminMutations.php",
|
||||
{
|
||||
method: "POST",
|
||||
|
||||
headers: {
|
||||
"Content-Type":
|
||||
"application/json"
|
||||
},
|
||||
|
||||
body: JSON.stringify({
|
||||
SaveKey: SaveKey,
|
||||
Action: "List"
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
const Result =
|
||||
await Response.json();
|
||||
|
||||
if (
|
||||
!Response.ok ||
|
||||
Result.Success !== true
|
||||
) {
|
||||
throw new Error(
|
||||
Result.Error ??
|
||||
"Couldn't load mutations for the Plant editor."
|
||||
);
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
async function LoadAdminPlants(
|
||||
PreferredPlantKey = null
|
||||
) {
|
||||
@@ -174,17 +256,28 @@ async function LoadAdminPlants(
|
||||
"Loading plants..."
|
||||
);
|
||||
|
||||
const Result =
|
||||
await AdminPlantRequest(
|
||||
const [
|
||||
PlantResult,
|
||||
MutationResult
|
||||
] = await Promise.all([
|
||||
AdminPlantRequest(
|
||||
"List"
|
||||
);
|
||||
),
|
||||
AdminPlantMutationRequest()
|
||||
]);
|
||||
|
||||
AdminPlantCatalogue =
|
||||
Result.Plants ?? {};
|
||||
PlantResult.Plants ?? {};
|
||||
|
||||
AdminPlantMutationCatalogue =
|
||||
MutationResult.Mutations ?? {};
|
||||
|
||||
Plants =
|
||||
AdminPlantCatalogue;
|
||||
|
||||
MutationSets =
|
||||
AdminPlantMutationCatalogue;
|
||||
|
||||
RenderAdminPlantSelect();
|
||||
|
||||
const Keys =
|
||||
@@ -331,13 +424,20 @@ function LoadAdminPlantIntoForm(
|
||||
: ""
|
||||
);
|
||||
|
||||
SetAdminPlantField(
|
||||
"AdminPlantGrowthTime",
|
||||
SetAdminPlantDurationFields(
|
||||
Number(
|
||||
Plant.GrowthTime ?? 0
|
||||
)
|
||||
);
|
||||
|
||||
SetAdminPlantField(
|
||||
"AdminPlantHarvestMultiplier",
|
||||
Number(
|
||||
Plant.HarvestMultiplier ??
|
||||
1.5
|
||||
)
|
||||
);
|
||||
|
||||
document.getElementById(
|
||||
"AdminPlantShopPlant"
|
||||
).checked =
|
||||
@@ -407,11 +507,15 @@ function StartNewAdminPlant() {
|
||||
""
|
||||
);
|
||||
|
||||
SetAdminPlantField(
|
||||
"AdminPlantGrowthTime",
|
||||
SetAdminPlantDurationFields(
|
||||
3600000
|
||||
);
|
||||
|
||||
SetAdminPlantField(
|
||||
"AdminPlantHarvestMultiplier",
|
||||
1.5
|
||||
);
|
||||
|
||||
document.getElementById(
|
||||
"AdminPlantShopPlant"
|
||||
).checked = false;
|
||||
@@ -583,6 +687,12 @@ function GetAdminPlantFormData() {
|
||||
).value
|
||||
),
|
||||
|
||||
HarvestMultiplier: Number(
|
||||
document.getElementById(
|
||||
"AdminPlantHarvestMultiplier"
|
||||
).value
|
||||
),
|
||||
|
||||
Effects:
|
||||
ParseAdminPlantEffects(),
|
||||
|
||||
@@ -705,15 +815,14 @@ function RenderAdminPlantPreview() {
|
||||
|
||||
Tile.replaceChildren();
|
||||
|
||||
const Images =
|
||||
PlantImages[
|
||||
PlantKey
|
||||
] ?? [];
|
||||
const MatureImage =
|
||||
GetPlantMatureImageSource(
|
||||
AdminPlantCatalogue[
|
||||
PlantKey
|
||||
] ?? PlantKey
|
||||
);
|
||||
|
||||
if (
|
||||
Array.isArray(Images) &&
|
||||
Images.length > 0
|
||||
) {
|
||||
if (MatureImage !== null) {
|
||||
const Image =
|
||||
document.createElement(
|
||||
"img"
|
||||
@@ -723,9 +832,7 @@ function RenderAdminPlantPreview() {
|
||||
"PlantSprite";
|
||||
|
||||
Image.src =
|
||||
Images[
|
||||
Images.length - 1
|
||||
];
|
||||
MatureImage;
|
||||
|
||||
Image.alt = Name;
|
||||
|
||||
@@ -771,11 +878,769 @@ function RenderAdminPlantPreview() {
|
||||
document.getElementById(
|
||||
"AdminPlantGrowthTimeHint"
|
||||
).textContent =
|
||||
Number.isFinite(GrowthTime)
|
||||
? GrowthTime.toLocaleString() +
|
||||
" ms stored"
|
||||
: "";
|
||||
|
||||
RenderAdminPlantEconomyPreview();
|
||||
RenderAdminPlantImageManager();
|
||||
}
|
||||
|
||||
|
||||
function SetAdminPlantDurationFields(
|
||||
Milliseconds
|
||||
) {
|
||||
Milliseconds = Math.max(
|
||||
0,
|
||||
Number(Milliseconds) || 0
|
||||
);
|
||||
|
||||
const TotalSeconds =
|
||||
Math.round(
|
||||
Milliseconds / 1000
|
||||
);
|
||||
|
||||
SetAdminPlantField(
|
||||
"AdminPlantGrowthHours",
|
||||
Math.floor(
|
||||
TotalSeconds / 3600
|
||||
)
|
||||
);
|
||||
|
||||
SetAdminPlantField(
|
||||
"AdminPlantGrowthMinutes",
|
||||
Math.floor(
|
||||
(TotalSeconds % 3600) / 60
|
||||
)
|
||||
);
|
||||
|
||||
SetAdminPlantField(
|
||||
"AdminPlantGrowthSeconds",
|
||||
TotalSeconds % 60
|
||||
);
|
||||
|
||||
SetAdminPlantField(
|
||||
"AdminPlantGrowthTime",
|
||||
TotalSeconds * 1000
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function SyncAdminPlantGrowthTime() {
|
||||
const Hours = Number(
|
||||
document.getElementById(
|
||||
"AdminPlantGrowthHours"
|
||||
).value
|
||||
);
|
||||
|
||||
const Minutes = Number(
|
||||
document.getElementById(
|
||||
"AdminPlantGrowthMinutes"
|
||||
).value
|
||||
);
|
||||
|
||||
const Seconds = Number(
|
||||
document.getElementById(
|
||||
"AdminPlantGrowthSeconds"
|
||||
).value
|
||||
);
|
||||
|
||||
const Values = [
|
||||
Hours,
|
||||
Minutes,
|
||||
Seconds
|
||||
];
|
||||
|
||||
if (
|
||||
Values.some(
|
||||
Value =>
|
||||
!Number.isFinite(Value)
|
||||
)
|
||||
) {
|
||||
SetAdminPlantField(
|
||||
"AdminPlantGrowthTime",
|
||||
""
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SetAdminPlantField(
|
||||
"AdminPlantGrowthTime",
|
||||
Math.max(0, Hours) * 3600000 +
|
||||
Math.max(0, Minutes) * 60000 +
|
||||
Math.max(0, Seconds) * 1000
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function GetAdminPlantPreviewDefinition() {
|
||||
const PlantId = Number(
|
||||
document.getElementById(
|
||||
"AdminPlantId"
|
||||
).value
|
||||
);
|
||||
|
||||
const PlantKey =
|
||||
document.getElementById(
|
||||
"AdminPlantKey"
|
||||
).value.trim();
|
||||
|
||||
const Existing =
|
||||
AdminPlantCatalogue[
|
||||
PlantKey
|
||||
] ?? {};
|
||||
|
||||
const ShopPlant =
|
||||
document.getElementById(
|
||||
"AdminPlantShopPlant"
|
||||
).checked;
|
||||
|
||||
const BaseCostValue =
|
||||
document.getElementById(
|
||||
"AdminPlantBaseCost"
|
||||
).value;
|
||||
|
||||
return {
|
||||
...Existing,
|
||||
Id: PlantId,
|
||||
Name:
|
||||
document.getElementById(
|
||||
"AdminPlantName"
|
||||
).value.trim() ||
|
||||
"New plant",
|
||||
Tags: ParseAdminPlantTags(),
|
||||
GrowthTime: Number(
|
||||
document.getElementById(
|
||||
"AdminPlantGrowthTime"
|
||||
).value
|
||||
),
|
||||
HarvestMultiplier: Number(
|
||||
document.getElementById(
|
||||
"AdminPlantHarvestMultiplier"
|
||||
).value
|
||||
),
|
||||
Shop: {
|
||||
ShopPlant: ShopPlant,
|
||||
BaseCost:
|
||||
ShopPlant &&
|
||||
BaseCostValue !== ""
|
||||
? Number(BaseCostValue)
|
||||
: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function RenderAdminPlantEconomyPreview() {
|
||||
const Plant =
|
||||
GetAdminPlantPreviewDefinition();
|
||||
|
||||
const PlantKey =
|
||||
document.getElementById(
|
||||
"AdminPlantKey"
|
||||
).value.trim();
|
||||
|
||||
const PreviewCatalogue = {
|
||||
...AdminPlantCatalogue,
|
||||
[
|
||||
PlantKey ||
|
||||
"__AdminPlantPreview"
|
||||
]: Plant
|
||||
};
|
||||
|
||||
const CostInfo =
|
||||
Number.isFinite(Plant.Id) &&
|
||||
Plant.Id > 0
|
||||
? GetCataloguePlantCostInfo(
|
||||
Plant.Id,
|
||||
PreviewCatalogue,
|
||||
AdminPlantMutationCatalogue
|
||||
)
|
||||
: null;
|
||||
|
||||
const Multiplier =
|
||||
GetPlantHarvestMultiplier(
|
||||
Plant
|
||||
);
|
||||
|
||||
const GrowthTime = Number(
|
||||
Plant.GrowthTime
|
||||
);
|
||||
|
||||
const Cost =
|
||||
CostInfo?.Cost ?? null;
|
||||
|
||||
const Reward =
|
||||
Cost === null
|
||||
? null
|
||||
: Math.ceil(
|
||||
Cost * Multiplier
|
||||
);
|
||||
|
||||
const Profit =
|
||||
Cost === null ||
|
||||
Reward === null
|
||||
? null
|
||||
: Reward - Cost;
|
||||
|
||||
const Dph =
|
||||
Profit === null ||
|
||||
!Number.isFinite(GrowthTime) ||
|
||||
GrowthTime <= 0
|
||||
? null
|
||||
: Profit * 3600000 /
|
||||
GrowthTime;
|
||||
|
||||
SetAdminPlantEconomyText(
|
||||
"AdminPlantEconomyCost",
|
||||
FormatAdminPlantDew(Cost)
|
||||
);
|
||||
|
||||
SetAdminPlantEconomyText(
|
||||
"AdminPlantEconomyMultiplier",
|
||||
Multiplier.toLocaleString(
|
||||
undefined,
|
||||
{maximumFractionDigits: 4}
|
||||
) + "×"
|
||||
);
|
||||
|
||||
SetAdminPlantEconomyText(
|
||||
"AdminPlantEconomyReward",
|
||||
FormatAdminPlantDew(Reward)
|
||||
);
|
||||
|
||||
SetAdminPlantEconomyText(
|
||||
"AdminPlantEconomyProfit",
|
||||
FormatAdminPlantDew(Profit)
|
||||
);
|
||||
|
||||
SetAdminPlantEconomyText(
|
||||
"AdminPlantEconomyGrowth",
|
||||
Number.isFinite(GrowthTime)
|
||||
? FormatAdminDuration(
|
||||
GrowthTime
|
||||
)
|
||||
: "Unavailable"
|
||||
);
|
||||
|
||||
SetAdminPlantEconomyText(
|
||||
"AdminPlantEconomyDph",
|
||||
Dph === null
|
||||
? "Unavailable"
|
||||
: Dph.toLocaleString(
|
||||
undefined,
|
||||
{maximumFractionDigits: 2}
|
||||
) + " Dew/h"
|
||||
);
|
||||
|
||||
let Source = "Unavailable";
|
||||
|
||||
if (CostInfo?.SourceType === "Base") {
|
||||
Source = "Base seed cost";
|
||||
} else if (
|
||||
CostInfo?.SourceType ===
|
||||
"Mutation"
|
||||
) {
|
||||
Source =
|
||||
CostInfo.SourceMutation
|
||||
?.Name ??
|
||||
CostInfo.SourceMutation
|
||||
?.MutationKey ??
|
||||
"Mutation";
|
||||
}
|
||||
|
||||
SetAdminPlantEconomyText(
|
||||
"AdminPlantEconomySource",
|
||||
Source
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function SetAdminPlantEconomyText(
|
||||
ElementId,
|
||||
Value
|
||||
) {
|
||||
document.getElementById(
|
||||
ElementId
|
||||
).textContent = Value;
|
||||
}
|
||||
|
||||
|
||||
function FormatAdminPlantDew(
|
||||
Value
|
||||
) {
|
||||
return (
|
||||
Value === null ||
|
||||
!Number.isFinite(Value)
|
||||
)
|
||||
? "Unavailable"
|
||||
: Math.ceil(Value)
|
||||
.toLocaleString() +
|
||||
" Dew";
|
||||
}
|
||||
|
||||
|
||||
function RenderAdminPlantImageManager() {
|
||||
const List =
|
||||
document.getElementById(
|
||||
"AdminPlantImageList"
|
||||
);
|
||||
|
||||
const Note =
|
||||
document.getElementById(
|
||||
"AdminPlantImageNote"
|
||||
);
|
||||
|
||||
const UploadButton =
|
||||
document.getElementById(
|
||||
"AdminPlantImageUploadButton"
|
||||
);
|
||||
|
||||
const FileInput =
|
||||
document.getElementById(
|
||||
"AdminPlantImageFiles"
|
||||
);
|
||||
|
||||
if (
|
||||
List === null ||
|
||||
Note === null ||
|
||||
UploadButton === null ||
|
||||
FileInput === null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
List.replaceChildren();
|
||||
|
||||
const Plant =
|
||||
AdminPlantEditingKey === null
|
||||
? null
|
||||
: AdminPlantCatalogue[
|
||||
AdminPlantEditingKey
|
||||
] ?? null;
|
||||
|
||||
const CanEdit =
|
||||
Plant !== null &&
|
||||
!AdminPlantImagePending;
|
||||
|
||||
UploadButton.disabled =
|
||||
!CanEdit;
|
||||
|
||||
FileInput.disabled =
|
||||
!CanEdit;
|
||||
|
||||
if (Plant === null) {
|
||||
Note.textContent =
|
||||
"Save the plant first, then upload sprites whenever you have them.";
|
||||
return;
|
||||
}
|
||||
|
||||
const StaticImages =
|
||||
Array.isArray(
|
||||
PlantImages[
|
||||
AdminPlantEditingKey
|
||||
]
|
||||
)
|
||||
? PlantImages[
|
||||
AdminPlantEditingKey
|
||||
]
|
||||
: [];
|
||||
|
||||
const ApiStages =
|
||||
Array.isArray(
|
||||
Plant.ImageStages
|
||||
)
|
||||
? [...Plant.ImageStages]
|
||||
.sort(
|
||||
(A, B) =>
|
||||
Number(A.Stage) -
|
||||
Number(B.Stage)
|
||||
)
|
||||
: [];
|
||||
|
||||
const FallbackText =
|
||||
StaticImages.length > 0
|
||||
? " Repository fallback: " +
|
||||
StaticImages.length +
|
||||
" stage" +
|
||||
(
|
||||
StaticImages.length === 1
|
||||
? "."
|
||||
: "s."
|
||||
)
|
||||
: "";
|
||||
|
||||
if (ApiStages.length === 0) {
|
||||
Note.textContent =
|
||||
"No API sprites uploaded." +
|
||||
FallbackText;
|
||||
return;
|
||||
}
|
||||
|
||||
Note.textContent =
|
||||
ApiStages.length +
|
||||
" API sprite" +
|
||||
(
|
||||
ApiStages.length === 1
|
||||
? " uploaded."
|
||||
: "s uploaded."
|
||||
) +
|
||||
FallbackText;
|
||||
|
||||
for (const ImageStage of ApiStages) {
|
||||
const Stage = Number(
|
||||
ImageStage.Stage
|
||||
);
|
||||
|
||||
const Row =
|
||||
document.createElement(
|
||||
"div"
|
||||
);
|
||||
|
||||
Row.className =
|
||||
"AdminPlantImageRow";
|
||||
|
||||
const Preview =
|
||||
document.createElement(
|
||||
"div"
|
||||
);
|
||||
|
||||
Preview.className =
|
||||
"PlantTile AdminPlantImagePreview";
|
||||
|
||||
const Image =
|
||||
document.createElement(
|
||||
"img"
|
||||
);
|
||||
|
||||
Image.className =
|
||||
"PlantSprite";
|
||||
|
||||
Image.src =
|
||||
GetAdminPlantApiImageUrl(
|
||||
Plant.Id,
|
||||
Stage,
|
||||
ImageStage.Revision
|
||||
);
|
||||
|
||||
Image.alt =
|
||||
Plant.Name +
|
||||
" stage " +
|
||||
Stage;
|
||||
|
||||
Preview.appendChild(Image);
|
||||
|
||||
const Label =
|
||||
document.createElement(
|
||||
"strong"
|
||||
);
|
||||
|
||||
Label.textContent =
|
||||
"Stage " + Stage;
|
||||
|
||||
const RemoveButton =
|
||||
document.createElement(
|
||||
"button"
|
||||
);
|
||||
|
||||
RemoveButton.type = "button";
|
||||
RemoveButton.className =
|
||||
"ActionButton AdminInlineButton AdminPlantImageRemoveButton";
|
||||
RemoveButton.textContent =
|
||||
"Remove";
|
||||
RemoveButton.disabled =
|
||||
AdminPlantImagePending;
|
||||
|
||||
RemoveButton.addEventListener(
|
||||
"click",
|
||||
() =>
|
||||
DeleteAdminPlantImage(
|
||||
Plant.Id,
|
||||
Stage
|
||||
)
|
||||
);
|
||||
|
||||
Row.append(
|
||||
Preview,
|
||||
Label,
|
||||
RemoveButton
|
||||
);
|
||||
|
||||
List.appendChild(Row);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function GetAdminPlantApiImageUrl(
|
||||
PlantId,
|
||||
Stage,
|
||||
Revision = 0
|
||||
) {
|
||||
const Query =
|
||||
new URLSearchParams({
|
||||
PlantId: String(PlantId),
|
||||
Stage: String(Stage),
|
||||
v: String(Revision ?? 0)
|
||||
});
|
||||
|
||||
return ApiUrl +
|
||||
"/PlantImage.php?" +
|
||||
Query.toString();
|
||||
}
|
||||
|
||||
|
||||
async function UploadAdminPlantImages() {
|
||||
if (
|
||||
AdminPlantImagePending ||
|
||||
AdminPlantEditingKey === null
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Plant =
|
||||
AdminPlantCatalogue[
|
||||
AdminPlantEditingKey
|
||||
];
|
||||
|
||||
if (Plant === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const FileInput =
|
||||
document.getElementById(
|
||||
"AdminPlantImageFiles"
|
||||
);
|
||||
|
||||
const Files =
|
||||
Array.from(
|
||||
FileInput.files ?? []
|
||||
);
|
||||
|
||||
if (Files.length === 0) {
|
||||
SetAdminPlantMessage(
|
||||
"Choose one or more PNG sprites first."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const Stages = new Set();
|
||||
const Uploads = [];
|
||||
|
||||
try {
|
||||
for (const File of Files) {
|
||||
const Match =
|
||||
/^(\\d+)\\.png$/i.exec(
|
||||
File.name
|
||||
);
|
||||
|
||||
if (Match === null) {
|
||||
throw new Error(
|
||||
"Sprite filenames must be stage numbers such as 1.png or 3.png."
|
||||
);
|
||||
}
|
||||
|
||||
const Stage = Number(
|
||||
Match[1]
|
||||
);
|
||||
|
||||
if (
|
||||
!Number.isInteger(Stage) ||
|
||||
Stage < 1 ||
|
||||
Stage > 255
|
||||
) {
|
||||
throw new Error(
|
||||
"Sprite stages must be between 1 and 255."
|
||||
);
|
||||
}
|
||||
|
||||
if (Stages.has(Stage)) {
|
||||
throw new Error(
|
||||
"Stage " +
|
||||
Stage +
|
||||
" was selected more than once."
|
||||
);
|
||||
}
|
||||
|
||||
Stages.add(Stage);
|
||||
|
||||
Uploads.push({
|
||||
Stage: Stage,
|
||||
File: File
|
||||
});
|
||||
}
|
||||
} catch (Error) {
|
||||
SetAdminPlantMessage(
|
||||
Error.message
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
AdminPlantImagePending = true;
|
||||
RenderAdminPlantImageManager();
|
||||
|
||||
SetAdminPlantMessage(
|
||||
"Uploading sprites..."
|
||||
);
|
||||
|
||||
try {
|
||||
for (const Upload of Uploads) {
|
||||
const DataBase64 =
|
||||
await ReadAdminPlantFileBase64(
|
||||
Upload.File
|
||||
);
|
||||
|
||||
await AdminPlantRequest(
|
||||
"UploadImage",
|
||||
{
|
||||
PlantId: Plant.Id,
|
||||
Stage: Upload.Stage,
|
||||
MimeType: "image/png",
|
||||
DataBase64: DataBase64
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
FileInput.value = "";
|
||||
|
||||
localStorage.removeItem(
|
||||
"SarahtoninGardenContent"
|
||||
);
|
||||
|
||||
await LoadAdminPlants(
|
||||
AdminPlantEditingKey
|
||||
);
|
||||
|
||||
SetAdminPlantMessage(
|
||||
Uploads.length === 1
|
||||
? "Sprite uploaded."
|
||||
: Uploads.length +
|
||||
" sprites uploaded."
|
||||
);
|
||||
} catch (Error) {
|
||||
console.error(
|
||||
"Couldn't upload plant sprites:",
|
||||
Error
|
||||
);
|
||||
|
||||
SetAdminPlantMessage(
|
||||
Error.message ??
|
||||
"Couldn't upload plant sprites."
|
||||
);
|
||||
} finally {
|
||||
AdminPlantImagePending = false;
|
||||
RenderAdminPlantImageManager();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function ReadAdminPlantFileBase64(
|
||||
File
|
||||
) {
|
||||
return new Promise(
|
||||
(Resolve, Reject) => {
|
||||
const Reader =
|
||||
new FileReader();
|
||||
|
||||
Reader.addEventListener(
|
||||
"load",
|
||||
() => {
|
||||
const Result =
|
||||
String(
|
||||
Reader.result ??
|
||||
""
|
||||
);
|
||||
|
||||
const CommaIndex =
|
||||
Result.indexOf(",");
|
||||
|
||||
if (CommaIndex < 0) {
|
||||
Reject(
|
||||
new Error(
|
||||
"Couldn't read sprite file."
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Resolve(
|
||||
Result.slice(
|
||||
CommaIndex + 1
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Reader.addEventListener(
|
||||
"error",
|
||||
() =>
|
||||
Reject(
|
||||
new Error(
|
||||
"Couldn't read sprite file."
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
Reader.readAsDataURL(File);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
async function DeleteAdminPlantImage(
|
||||
PlantId,
|
||||
Stage
|
||||
) {
|
||||
if (AdminPlantImagePending) {
|
||||
return;
|
||||
}
|
||||
|
||||
AdminPlantImagePending = true;
|
||||
RenderAdminPlantImageManager();
|
||||
|
||||
SetAdminPlantMessage(
|
||||
"Removing stage " +
|
||||
Stage +
|
||||
" sprite..."
|
||||
);
|
||||
|
||||
try {
|
||||
await AdminPlantRequest(
|
||||
"DeleteImage",
|
||||
{
|
||||
PlantId: PlantId,
|
||||
Stage: Stage
|
||||
}
|
||||
);
|
||||
|
||||
localStorage.removeItem(
|
||||
"SarahtoninGardenContent"
|
||||
);
|
||||
|
||||
await LoadAdminPlants(
|
||||
AdminPlantEditingKey
|
||||
);
|
||||
|
||||
SetAdminPlantMessage(
|
||||
"Stage " +
|
||||
Stage +
|
||||
" sprite removed."
|
||||
);
|
||||
} catch (Error) {
|
||||
console.error(
|
||||
"Couldn't remove plant sprite:",
|
||||
Error
|
||||
);
|
||||
|
||||
SetAdminPlantMessage(
|
||||
Error.message ??
|
||||
"Couldn't remove plant sprite."
|
||||
);
|
||||
} finally {
|
||||
AdminPlantImagePending = false;
|
||||
RenderAdminPlantImageManager();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const GameContentCacheName =
|
||||
"SarahtoninGardenContent";
|
||||
|
||||
const GameContentCacheSchemaVersion = 2;
|
||||
const GameContentCacheSchemaVersion = 3;
|
||||
|
||||
/*
|
||||
* Once content has been checked, page loads for the next
|
||||
|
||||
+327
-1
@@ -269,6 +269,14 @@ function GetPlantShopCost(
|
||||
Plant.Shop?.ShopPlant ===
|
||||
true
|
||||
) {
|
||||
if (
|
||||
Plant.Shop.BaseCost === null ||
|
||||
Plant.Shop.BaseCost === undefined ||
|
||||
Plant.Shop.BaseCost === ""
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const BaseCost = Number(
|
||||
Plant.Shop.BaseCost
|
||||
);
|
||||
@@ -599,6 +607,35 @@ function GetMutationTimeFee(
|
||||
}
|
||||
|
||||
|
||||
function GetPlantHarvestMultiplier(
|
||||
PlantId
|
||||
) {
|
||||
const Plant =
|
||||
typeof PlantId === "object" &&
|
||||
PlantId !== null
|
||||
? PlantId
|
||||
: GetPlantById(
|
||||
PlantId
|
||||
);
|
||||
|
||||
if (Plant === null) {
|
||||
return 1.5;
|
||||
}
|
||||
|
||||
const Multiplier = Number(
|
||||
Plant.HarvestMultiplier ??
|
||||
1.5
|
||||
);
|
||||
|
||||
return (
|
||||
Number.isFinite(Multiplier) &&
|
||||
Multiplier >= 0
|
||||
)
|
||||
? Multiplier
|
||||
: 1.5;
|
||||
}
|
||||
|
||||
|
||||
function GetPlantHarvestReward(
|
||||
SaveData,
|
||||
PlantId
|
||||
@@ -614,7 +651,10 @@ function GetPlantHarvestReward(
|
||||
}
|
||||
|
||||
return Math.ceil(
|
||||
Cost * 1.5
|
||||
Cost *
|
||||
GetPlantHarvestMultiplier(
|
||||
PlantId
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -699,3 +739,289 @@ function FormatDewPerHour(
|
||||
" Dew"
|
||||
);
|
||||
}
|
||||
|
||||
function GetCataloguePlantById(
|
||||
PlantId,
|
||||
PlantCatalogue = Plants
|
||||
) {
|
||||
PlantId = Number(PlantId);
|
||||
|
||||
for (
|
||||
const Plant
|
||||
of Object.values(
|
||||
PlantCatalogue
|
||||
)
|
||||
) {
|
||||
if (Number(Plant.Id) === PlantId) {
|
||||
return Plant;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function GetCataloguePlantCostInfo(
|
||||
PlantId,
|
||||
PlantCatalogue = Plants,
|
||||
MutationCatalogue = MutationSets,
|
||||
Cache = new Map(),
|
||||
Visiting = new Set()
|
||||
) {
|
||||
PlantId = Number(PlantId);
|
||||
|
||||
if (Cache.has(PlantId)) {
|
||||
return Cache.get(PlantId);
|
||||
}
|
||||
|
||||
const Plant =
|
||||
GetCataloguePlantById(
|
||||
PlantId,
|
||||
PlantCatalogue
|
||||
);
|
||||
|
||||
if (Plant === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Plant.Shop?.ShopPlant === true) {
|
||||
if (
|
||||
Plant.Shop.BaseCost === null ||
|
||||
Plant.Shop.BaseCost === undefined ||
|
||||
Plant.Shop.BaseCost === ""
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const BaseCost = Number(
|
||||
Plant.Shop.BaseCost
|
||||
);
|
||||
|
||||
if (
|
||||
!Number.isFinite(BaseCost) ||
|
||||
BaseCost < 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const Info = {
|
||||
Cost: Math.ceil(BaseCost),
|
||||
SourceType: "Base",
|
||||
SourceMutation: null
|
||||
};
|
||||
|
||||
Cache.set(PlantId, Info);
|
||||
return Info;
|
||||
}
|
||||
|
||||
if (Visiting.has(PlantId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Visiting.add(PlantId);
|
||||
|
||||
let CheapestInfo = null;
|
||||
|
||||
for (
|
||||
const [MutationKey, Mutation]
|
||||
of Object.entries(
|
||||
MutationCatalogue
|
||||
)
|
||||
) {
|
||||
if (Mutation.Archived === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!Mutation.Relations
|
||||
?.PlantsCreated
|
||||
?.includes(PlantId)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const IngredientCost =
|
||||
GetCatalogueMutationIngredientCost(
|
||||
Mutation,
|
||||
PlantCatalogue,
|
||||
MutationCatalogue,
|
||||
Cache,
|
||||
Visiting
|
||||
);
|
||||
|
||||
if (IngredientCost === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Cost =
|
||||
IngredientCost +
|
||||
GetMutationTimeFee(
|
||||
Mutation
|
||||
);
|
||||
|
||||
if (
|
||||
CheapestInfo === null ||
|
||||
Cost < CheapestInfo.Cost
|
||||
) {
|
||||
CheapestInfo = {
|
||||
Cost: Cost,
|
||||
SourceType: "Mutation",
|
||||
SourceMutation: {
|
||||
Id: Number(
|
||||
Mutation.Id
|
||||
),
|
||||
MutationKey: MutationKey,
|
||||
Name:
|
||||
Mutation.Name ??
|
||||
MutationKey
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Visiting.delete(PlantId);
|
||||
Cache.set(PlantId, CheapestInfo);
|
||||
|
||||
return CheapestInfo;
|
||||
}
|
||||
|
||||
|
||||
function GetCatalogueMutationIngredientCost(
|
||||
Mutation,
|
||||
PlantCatalogue,
|
||||
MutationCatalogue,
|
||||
Cache,
|
||||
Visiting
|
||||
) {
|
||||
let TotalCost = 0;
|
||||
|
||||
for (const Row of Mutation.Pattern ?? []) {
|
||||
for (const Matcher of Row) {
|
||||
if (
|
||||
Matcher === null ||
|
||||
Matcher === "Empty" ||
|
||||
Matcher === "Any"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const MatcherCost =
|
||||
GetCatalogueMatcherSeedCost(
|
||||
Matcher,
|
||||
PlantCatalogue,
|
||||
MutationCatalogue,
|
||||
Cache,
|
||||
Visiting
|
||||
);
|
||||
|
||||
if (MatcherCost === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TotalCost += MatcherCost;
|
||||
}
|
||||
}
|
||||
|
||||
return TotalCost;
|
||||
}
|
||||
|
||||
|
||||
function GetCatalogueMatcherSeedCost(
|
||||
Matcher,
|
||||
PlantCatalogue,
|
||||
MutationCatalogue,
|
||||
Cache,
|
||||
Visiting
|
||||
) {
|
||||
if (typeof Matcher === "string") {
|
||||
const Plant =
|
||||
PlantCatalogue[Matcher];
|
||||
|
||||
if (
|
||||
Plant === undefined ||
|
||||
Plant.Archived === true
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetCataloguePlantCostInfo(
|
||||
Plant.Id,
|
||||
PlantCatalogue,
|
||||
MutationCatalogue,
|
||||
Cache,
|
||||
Visiting
|
||||
)?.Cost ?? null;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof Matcher !== "object" ||
|
||||
Matcher === null
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof Matcher.Plant === "string") {
|
||||
const Plant =
|
||||
PlantCatalogue[Matcher.Plant];
|
||||
|
||||
if (
|
||||
Plant === undefined ||
|
||||
Plant.Archived === true
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetCataloguePlantCostInfo(
|
||||
Plant.Id,
|
||||
PlantCatalogue,
|
||||
MutationCatalogue,
|
||||
Cache,
|
||||
Visiting
|
||||
)?.Cost ?? null;
|
||||
}
|
||||
|
||||
let CheapestCost = null;
|
||||
|
||||
for (
|
||||
const Plant
|
||||
of Object.values(
|
||||
PlantCatalogue
|
||||
)
|
||||
) {
|
||||
if (Plant.Archived === true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
!DoesPlantMatchRecipeMatcher(
|
||||
Plant,
|
||||
Matcher
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Cost =
|
||||
GetCataloguePlantCostInfo(
|
||||
Plant.Id,
|
||||
PlantCatalogue,
|
||||
MutationCatalogue,
|
||||
Cache,
|
||||
Visiting
|
||||
)?.Cost ?? null;
|
||||
|
||||
if (Cost === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
CheapestCost === null ||
|
||||
Cost < CheapestCost
|
||||
) {
|
||||
CheapestCost = Cost;
|
||||
}
|
||||
}
|
||||
|
||||
return CheapestCost;
|
||||
}
|
||||
|
||||
@@ -1303,9 +1303,9 @@ function GetPlantImages(
|
||||
Plot,
|
||||
Plant
|
||||
) {
|
||||
return PlantImages[
|
||||
Plot.Plant
|
||||
] ?? [];
|
||||
return GetPlantImageSources(
|
||||
Plant ?? Plot.Plant
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -28,13 +28,17 @@ function RenderGuideRecipePlants() {
|
||||
const Plant =
|
||||
Plants[PlantKey];
|
||||
|
||||
const Images =
|
||||
PlantImages[PlantKey] ?? [];
|
||||
const MatureImage =
|
||||
Plant === undefined
|
||||
? null
|
||||
: GetPlantMatureImageSource(
|
||||
Plant
|
||||
);
|
||||
|
||||
|
||||
if (
|
||||
Plant === undefined ||
|
||||
Images.length === 0
|
||||
MatureImage === null
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@@ -49,9 +53,7 @@ function RenderGuideRecipePlants() {
|
||||
"PlantSprite GuideRecipeImage";
|
||||
|
||||
Image.src =
|
||||
Images[
|
||||
Images.length - 1
|
||||
];
|
||||
MatureImage;
|
||||
|
||||
Image.alt = "";
|
||||
|
||||
|
||||
@@ -1051,11 +1051,13 @@ function CreateMutationPlantCell(
|
||||
"PlantTile GuideRecipeCell GuideRecipePlant";
|
||||
|
||||
|
||||
const Images =
|
||||
PlantImages[PlantKey] ?? [];
|
||||
const MatureImage =
|
||||
GetPlantMatureImageSource(
|
||||
Plant
|
||||
);
|
||||
|
||||
|
||||
if (Images.length > 0) {
|
||||
if (MatureImage !== null) {
|
||||
const Image =
|
||||
document.createElement(
|
||||
"img"
|
||||
@@ -1065,9 +1067,7 @@ function CreateMutationPlantCell(
|
||||
"PlantSprite GuideRecipeImage";
|
||||
|
||||
Image.src =
|
||||
Images[
|
||||
Images.length - 1
|
||||
];
|
||||
MatureImage;
|
||||
|
||||
Image.alt =
|
||||
"";
|
||||
|
||||
@@ -178,20 +178,13 @@ function CreatePlantEncyclopediaVisual(
|
||||
"PlantTile PlantEncyclopediaVisual";
|
||||
|
||||
|
||||
const PlantKey =
|
||||
GetPlantKeyById(
|
||||
Plant.Id
|
||||
const MatureImage =
|
||||
GetPlantMatureImageSource(
|
||||
Plant
|
||||
);
|
||||
|
||||
const Images =
|
||||
PlantKey === null
|
||||
? []
|
||||
: PlantImages[
|
||||
PlantKey
|
||||
] ?? [];
|
||||
|
||||
|
||||
if (Images.length === 0) {
|
||||
if (MatureImage === null) {
|
||||
const Missing =
|
||||
document.createElement(
|
||||
"span"
|
||||
@@ -220,9 +213,7 @@ function CreatePlantEncyclopediaVisual(
|
||||
"PlantSprite";
|
||||
|
||||
Image.src =
|
||||
Images[
|
||||
Images.length - 1
|
||||
];
|
||||
MatureImage;
|
||||
|
||||
Image.alt =
|
||||
Plant.Name;
|
||||
|
||||
@@ -48,3 +48,140 @@ async function FetchPlantsFromApi() {
|
||||
|
||||
return Result.Plants;
|
||||
}
|
||||
|
||||
function GetPlantImageSources(
|
||||
PlantOrKey
|
||||
) {
|
||||
const PlantKey =
|
||||
typeof PlantOrKey === "string"
|
||||
? PlantOrKey
|
||||
: Object.entries(
|
||||
Plants
|
||||
).find(
|
||||
([, Candidate]) =>
|
||||
Candidate ===
|
||||
PlantOrKey ||
|
||||
Number(Candidate?.Id) ===
|
||||
Number(PlantOrKey?.Id)
|
||||
)?.[0] ?? null;
|
||||
|
||||
const Plant =
|
||||
typeof PlantOrKey === "string"
|
||||
? Plants[PlantOrKey]
|
||||
: PlantOrKey;
|
||||
|
||||
const StaticImages =
|
||||
PlantKey !== null &&
|
||||
typeof PlantKey === "string" &&
|
||||
Array.isArray(
|
||||
PlantImages[PlantKey]
|
||||
)
|
||||
? PlantImages[PlantKey]
|
||||
: [];
|
||||
|
||||
const ApiStages =
|
||||
Array.isArray(
|
||||
Plant?.ImageStages
|
||||
)
|
||||
? Plant.ImageStages
|
||||
: [];
|
||||
|
||||
let HighestStage =
|
||||
StaticImages.length;
|
||||
|
||||
for (const ImageStage of ApiStages) {
|
||||
const Stage = Number(
|
||||
typeof ImageStage === "object" &&
|
||||
ImageStage !== null
|
||||
? ImageStage.Stage
|
||||
: ImageStage
|
||||
);
|
||||
|
||||
if (
|
||||
Number.isInteger(Stage) &&
|
||||
Stage > HighestStage
|
||||
) {
|
||||
HighestStage = Stage;
|
||||
}
|
||||
}
|
||||
|
||||
const Images =
|
||||
Array.from(
|
||||
{length: HighestStage},
|
||||
(_, Index) =>
|
||||
StaticImages[Index] ?? null
|
||||
);
|
||||
|
||||
if (Plant === undefined || Plant === null) {
|
||||
return Images;
|
||||
}
|
||||
|
||||
for (const ImageStage of ApiStages) {
|
||||
const Stage = Number(
|
||||
typeof ImageStage === "object" &&
|
||||
ImageStage !== null
|
||||
? ImageStage.Stage
|
||||
: ImageStage
|
||||
);
|
||||
|
||||
if (
|
||||
!Number.isInteger(Stage) ||
|
||||
Stage < 1
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Revision =
|
||||
typeof ImageStage === "object" &&
|
||||
ImageStage !== null
|
||||
? Number(
|
||||
ImageStage.Revision ?? 0
|
||||
)
|
||||
: 0;
|
||||
|
||||
const Query =
|
||||
new URLSearchParams({
|
||||
PlantId: String(Plant.Id),
|
||||
Stage: String(Stage),
|
||||
v: String(
|
||||
Number.isFinite(Revision)
|
||||
? Revision
|
||||
: 0
|
||||
)
|
||||
});
|
||||
|
||||
Images[Stage - 1] =
|
||||
ApiUrl +
|
||||
"/PlantImage.php?" +
|
||||
Query.toString();
|
||||
}
|
||||
|
||||
return Images;
|
||||
}
|
||||
|
||||
|
||||
function GetPlantMatureImageSource(
|
||||
PlantOrKey
|
||||
) {
|
||||
const Images =
|
||||
GetPlantImageSources(
|
||||
PlantOrKey
|
||||
);
|
||||
|
||||
for (
|
||||
let Index = Images.length - 1;
|
||||
Index >= 0;
|
||||
Index--
|
||||
) {
|
||||
if (
|
||||
typeof Images[Index] ===
|
||||
"string" &&
|
||||
Images[Index].length > 0
|
||||
) {
|
||||
return Images[Index];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
+5
-14
@@ -951,20 +951,13 @@ function CreateShopPlantVisual(
|
||||
"PlantTile ShopPlantImageFrame";
|
||||
|
||||
|
||||
const PlantKey =
|
||||
GetPlantKeyById(
|
||||
Plant.Id
|
||||
const MatureImage =
|
||||
GetPlantMatureImageSource(
|
||||
Plant
|
||||
);
|
||||
|
||||
const Images =
|
||||
PlantKey === null
|
||||
? []
|
||||
: PlantImages[
|
||||
PlantKey
|
||||
] ?? [];
|
||||
|
||||
|
||||
if (Images.length === 0) {
|
||||
if (MatureImage === null) {
|
||||
const Missing =
|
||||
document.createElement(
|
||||
"span"
|
||||
@@ -989,9 +982,7 @@ function CreateShopPlantVisual(
|
||||
"PlantSprite";
|
||||
|
||||
Image.src =
|
||||
Images[
|
||||
Images.length - 1
|
||||
];
|
||||
MatureImage;
|
||||
|
||||
Image.alt =
|
||||
Plant.Name;
|
||||
|
||||
@@ -1884,3 +1884,129 @@ input, button, textarea, select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.AdminDurationInputs {
|
||||
display: grid;
|
||||
grid-template-columns: repeat( 3, minmax(0, 1fr) );
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.AdminDurationInputs label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.AdminDurationInputs label > span {
|
||||
color: var(--Overlay2);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.AdminPlantEconomyPreview {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.AdminPlantEconomyPreview h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--Peach);
|
||||
}
|
||||
|
||||
.AdminPlantEconomyPreview dl {
|
||||
display: grid;
|
||||
grid-template-columns: repeat( 3, minmax(0, 1fr) );
|
||||
gap: 0.65rem 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.AdminPlantEconomyPreview dl > div {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.AdminPlantEconomyPreview dt {
|
||||
color: var(--Overlay2);
|
||||
}
|
||||
|
||||
.AdminPlantEconomyPreview dd {
|
||||
margin: 0.15rem 0 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.AdminPlantEconomySource {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.AdminPlantImagesSection {
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 3px solid var(--Surface2);
|
||||
}
|
||||
|
||||
.AdminPlantImagesSection h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.AdminPlantImageUpload {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.AdminPlantImageUpload input[type="file"] {
|
||||
flex: 1 1 18rem;
|
||||
}
|
||||
|
||||
.AdminPlantImageNote {
|
||||
color: var(--Overlay2);
|
||||
}
|
||||
|
||||
.AdminPlantImageList {
|
||||
display: grid;
|
||||
gap: 0.65rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.AdminPlantImageRow {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
padding: 0.65rem;
|
||||
border: 3px solid var(--Surface1);
|
||||
}
|
||||
|
||||
.AdminPlantImagePreview {
|
||||
display: flex;
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.AdminPlantImageRemoveButton {
|
||||
--ActionAccent: var(--Red);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.AdminDurationInputs,
|
||||
.AdminPlantEconomyPreview dl {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.AdminPlantEconomySource {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.AdminPlantImageRow {
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.AdminPlantImageRemoveButton {
|
||||
grid-column: 1 / -1;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user