1
0
Fork 0
forked from Simnation/Main
Main/resources/[creator]/drugs_creator/html/index.js

3569 lines
123 KiB
JavaScript
Raw Permalink Normal View History

2025-06-07 08:51:21 +02:00
const resName = GetParentResourceName();
// Utils
function maxTwoDecimals() {
if(isNaN(this.value)) {
return
}
let number = parseFloat(this.value);
if(number) {
this.value = number.toFixed(2);
}
}
$(".max-two-decimals").change(maxTwoDecimals)
function componentToHex(c) {
var hex = c.toString(16);
return hex.length == 1 ? "0" + hex : hex;
}
function rgbToHex(r, g, b) {
return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
}
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
// Harvestable items
let harvestableItemsDatatable = $("#harvestable-items-container").DataTable( {
"lengthMenu": [10, 15, 20],
"createdRow": function ( row, data, index ) {
$(row).addClass("clickable");
$(row).click(function() {
let harvestableItemId = parseInt(data[0]);
editHarvestableItem(harvestableItemId);
})
},
} );
let harvestableItems = {};
function loadHarvestableItems() {
$.post(`https://${resName}/getAllHarvestableItems`, {}, async function(rawHarvestableItems) {
// Manually create the table to avoid incompatibilities due table indexing
harvestableItems = {};
for(const[k, itemData] of Object.entries(rawHarvestableItems)) {
harvestableItems[itemData.id] = itemData;
}
harvestableItemsDatatable.clear();
for(const[itemId, itemData] of Object.entries(harvestableItems)) {
harvestableItemsDatatable.row.add([
itemId,
await getItemLabel(itemData.name),
itemData.minQuantity,
itemData.maxQuantity,
itemData.coords.x,
itemData.coords.y,
itemData.coords.z,
])
}
harvestableItemsDatatable.draw()
})
}
// Harvestable items creation
$("#new-harvestable-item-btn").click(function() {
let harvestableModal = $("#harvestable-item-modal");
harvestableModal.data("action", "create");
harvestableModal.find("input").val("");
$("#harvestable-scale-x").val(1.5);
$("#harvestable-scale-y").val(1.5);
$("#harvestable-scale-z").val(1.5);
$("#harvestable-icon-type").val(1);
$("#harvestable-min-quantity").val(1);
$("#harvestable-max-quantity").val(1);
$("#harvestable-harvest-time").val(5);
// Converts from edit modal to create modal
$("#delete-harvestable-item-btn").hide();
$("#save-harvestable-item-btn").text( getLocalizedText("menu:create") );
$("#harvestable-item-map-blip").change();
harvestableModal.modal("show");
})
$("#harvestable-choose-item-btn").click(async function() {
const itemName = await itemsDialog();
$("#harvestable-item-name").val(itemName);
})
$("#harvestable-current-coords-btn").click(async function(event) {
if(event.shiftKey) {
const coordsInUI = {
x: parseFloat( $("#harvestable-coords-x").val() ),
y: parseFloat( $("#harvestable-coords-y").val() ),
z: parseFloat( $("#harvestable-coords-z").val() )
}
teleportToCoords(coordsInUI);
return;
}
let coords = await getCurrentCoords()
$("#harvestable-coords-x").val(coords.x);
$("#harvestable-coords-y").val(coords.y);
$("#harvestable-coords-z").val(coords.z);
})
$("#harvestable-item-map-blip").change(function() {
let enabled = $(this).prop("checked");
$("#harvestable-item-map-blip-inputs").find("input").prop("disabled", !enabled);
})
$("#harvestable-form").submit(async function(event) {
if(isThereAnyErrorInForm(event)) return;
let harvestableItem = {
name: $("#harvestable-item-name").val(),
iconType: parseInt( $("#harvestable-icon-type").val() ),
coords: {
x: parseFloat( $("#harvestable-coords-x").val() ),
y: parseFloat( $("#harvestable-coords-y").val() ),
z: parseFloat( $("#harvestable-coords-z").val() ),
},
scale: {
x: parseFloat( $("#harvestable-scale-x").val() ),
y: parseFloat( $("#harvestable-scale-y").val() ),
z: parseFloat( $("#harvestable-scale-z").val() ),
},
bounce: $("#harvestable-bounce").prop("checked"),
followCamera: $("#harvestable-follow-camera").prop("checked"),
rotate: $("#harvestable-rotate").prop("checked"),
color: hexToRgb( $("#harvestable-color").val() ),
opacity: parseInt( $("#harvestable-opacity").val() ),
timeToHarvest: parseInt( $("#harvestable-harvest-time").val() ),
minQuantity: parseInt( $("#harvestable-min-quantity").val() ),
maxQuantity: parseInt( $("#harvestable-max-quantity").val() )
}
let isBlipEnabled = $("#harvestable-item-map-blip").prop("checked");
if( isBlipEnabled ) {
harvestableItem.blipName = $("#harvestable-item-blip-name").val();
harvestableItem.blipSprite = parseInt( $("#harvestable-item-sprite-id").val() );
harvestableItem.blipColor = parseInt( $("#harvestable-item-blip-color").val() );
harvestableItem.blipScale = parseFloat( $("#harvestable-item-blip-scale").val() );
}
let harvestableItemModal = $("#harvestable-item-modal");
let action = $(harvestableItemModal).data("action");
let response = null;
switch(action) {
case "create": {
response = await $.post(`https://${resName}/createNewHarvestableItem`, JSON.stringify(harvestableItem));
break;
}
case "edit": {
let itemId = harvestableItemModal.data("itemId");
response = await $.post(`https://${resName}/updateHarvestableItem`, JSON.stringify({
itemId: itemId,
itemData: harvestableItem
}));
break;
}
}
harvestableItemModal.modal("hide");
loadHarvestableItems();
showServerResponse(response);
})
// Harvestable items edit
function editHarvestableItem(harvestableItemId) {
let itemData = harvestableItems[harvestableItemId];
$("#harvestable-item-name").val(itemData.name);
$("#harvestable-icon-type").val(itemData.iconType);
$("#harvestable-coords-x").val(itemData.coords.x);
$("#harvestable-coords-y").val(itemData.coords.y);
$("#harvestable-coords-z").val(itemData.coords.z);
$("#harvestable-scale-x").val(itemData.scale.x);
$("#harvestable-scale-y").val(itemData.scale.y);
$("#harvestable-scale-z").val(itemData.scale.z);
$("#harvestable-bounce").prop("checked", itemData.bounce);
$("#harvestable-follow-camera").prop("checked", itemData.followCamera);
$("#harvestable-rotate").prop("checked", itemData.rotate);
$("#harvestable-color").val( rgbToHex(itemData.color.r, itemData.color.g, itemData.color.b) );
$("#harvestable-opacity").val(itemData.opacity);
$("#harvestable-harvest-time").val(itemData.timeToHarvest);
$("#harvestable-min-quantity").val(itemData.minQuantity);
$("#harvestable-max-quantity").val(itemData.maxQuantity);
let mapBlipCheckbox = $("#harvestable-item-map-blip");
if(itemData.blipSprite) {
$("#harvestable-item-blip-name").val(itemData.blipName);
$("#harvestable-item-sprite-id").val(itemData.blipSprite);
$("#harvestable-item-blip-color").val(itemData.blipColor);
$("#harvestable-item-blip-scale").val(itemData.blipScale);
mapBlipCheckbox.prop("checked", true);
} else {
mapBlipCheckbox.prop("checked", false);
}
mapBlipCheckbox.change();
let harvestableModal = $("#harvestable-item-modal");
harvestableModal.data("itemId", harvestableItemId);
harvestableModal.data("action", "edit");
$("#delete-harvestable-item-btn").show();
$("#save-harvestable-item-btn").text( getLocalizedText("menu:save") );
harvestableModal.modal("show");
}
// Harvestable item delete
$("#delete-harvestable-item-btn").click(async function() {
if(!await confirmDeletion()) return;
let itemId = $("#harvestable-item-modal").data("itemId");
const response = await $.post(`https://${resName}/deleteHarvestableItem`, JSON.stringify({
itemId: itemId
}));
$("#harvestable-item-modal").modal("hide");
loadHarvestableItems();
showServerResponse(response);
})
// Drugs Fields
let drugsFieldsDatatable = $("#fields-container").DataTable({
"lengthMenu": [10, 15, 20],
"createdRow": function ( row, data, index ) {
$(row).addClass("clickable");
$(row).click(function() {
let fieldId = parseInt(data[0]);
editDrugField(fieldId);
})
},
} );
let drugsFields = {};
function loadDrugsFields() {
$.post(`https://${resName}/getAllDrugsFields`, {}, function(rawDrugsFields) {
// Manually create the table to avoid incompatibilities due table indexing
drugsFields = {};
for(const[k, fieldData] of Object.entries(rawDrugsFields)) {
drugsFields[fieldData.id] = fieldData;
}
drugsFieldsDatatable.clear();
for(const[fieldId, fieldData] of Object.entries(drugsFields)) {
drugsFieldsDatatable.row.add([
fieldId,
fieldData.label,
fieldData.radius,
fieldData.coords.x,
fieldData.coords.y,
fieldData.coords.z,
])
}
drugsFieldsDatatable.draw()
})
}
$("#new-field-btn").click(function() {
let fieldModal = $("#field-modal");
fieldModal.data("action", "create");
fieldModal.find("input").val("");
$("#field-map-blip").prop("checked", false).change();
// Converts from edit modal to create modal
$("#delete-field-btn").hide();
$("#save-field-btn").text( getLocalizedText("menu:create") );
$("#fields-items").empty();
addItemToField();
fieldModal.modal("show");
})
$("#field-current-coords-btn").click(async function(event) {
if(event.shiftKey) {
const coordsInUI = {
x: parseFloat( $("#field-coords-x").val() ),
y: parseFloat( $("#field-coords-y").val() ),
z: parseFloat( $("#field-coords-z").val() )
}
teleportToCoords(coordsInUI);
return;
}
let coords = await getCurrentCoords();
$("#field-coords-x").val(coords.x);
$("#field-coords-y").val(coords.y);
$("#field-coords-z").val(coords.z);
});
$("#field-required-item-choose-btn").click(async function() {
const itemName = await itemsDialog();
$("#field-required-item-name").val(itemName);
})
$("#field-required-item-toggle").change(function() {
let enabled = $(this).prop("checked");
$("#field-required-item-choose-btn").prop("disabled", !enabled);
$("#field-required-item-lose-on-use").prop("disabled", !enabled);
$("#field-required-item-name").prop("disabled", !enabled).prop("required", enabled);
})
async function addItemToField(itemData = {}) {
let itemDiv = $(`
<div class="item">
<div class="row g-2 row-cols-auto my-1 align-items-center justify-content-center">
<div class="form-floating">
<input type="text" class="form-control field-item-label" placeholder="Label" value="${itemData.name ? await getItemLabel(itemData.name) : ""}" disabled>
<label>${ getLocalizedText("menu:label") }</label>
</div>
<div class="form-floating">
<input type="text" class="form-control field-item-name" placeholder="Item name" value="${itemData.name || ""}" required>
<label>${ getLocalizedText("menu:item_name") }</label>
</div>
<button type="button" class="btn btn-secondary col-auto choose-item-btn me-3" data-bs-toggle="tooltip" data-bs-placement="top" title="${ getLocalizedText("menu:choose_item") }"><i class="bi bi-list-ul"></i></button>
<div class="form-floating">
<input type="number" class="form-control" id="field-item-chances" placeholder="Chances" value="${itemData.chances}" required>
<label>${ getLocalizedText("menu:chances") }</label>
</div>
<button type="button" class="btn-close btn-close remove-btn ms-3"></button>
</div>
<div class="row g-2 row-cols-2 my-3">
<div class="form-floating">
<input type="number" class="form-control field-item-min-quantity" placeholder="Min quantity" value="${itemData.minQuantity}" required>
<label>${ getLocalizedText("menu:minimum_quantity") }</label>
</div>
<div class="form-floating">
<input type="number" class="form-control field-item-max-quantity" placeholder="Max quantity" value="${itemData.maxQuantity}" required>
<label>${ getLocalizedText("menu:maximum_quantity") }</label>
</div>
</div>
</div>
<hr>
`);
itemDiv.find(".choose-item-btn").click(async function() {
const itemName = await itemsDialog();
itemDiv.find(".field-item-name").val(itemName);
let itemLabel = await getItemLabel(itemName);
itemDiv.find(".field-item-label").val(itemLabel);
}).tooltip();
itemDiv.find(".remove-btn").click(function() {
itemDiv.remove();
})
$("#fields-items").append(itemDiv);
}
$("#field-add-item").click(addItemToField);
function getItemFieldDataFromDiv(itemDiv) {
let itemData = {
name: itemDiv.find(".field-item-name").val(),
chances: parseInt( itemDiv.find("#field-item-chances").val() ),
minQuantity: parseInt( itemDiv.find(".field-item-min-quantity").val() ),
maxQuantity: parseInt( itemDiv.find(".field-item-max-quantity").val() )
}
return itemData;
}
function getAllItemsFieldData() {
let items = [];
$("#fields-items").children(".item").each(function() {
items.push(getItemFieldDataFromDiv( $(this) ))
})
return items;
}
function editDrugField(fieldId) {
let fieldModal = $("#field-modal");
fieldModal.data("action", "edit");
fieldModal.data("fieldId", fieldId);
let fieldData = drugsFields[fieldId];
$("#field-label").val(fieldData.label);
$("#field-radius").val(fieldData.radius);
$("#field-coords-x").val(fieldData.coords.x);
$("#field-coords-y").val(fieldData.coords.y);
$("#field-coords-z").val(fieldData.coords.z);
$("#field-object-model").val(fieldData.objectModel);
$("#field-max-objects").val(fieldData.maxObjects);
$("#field-seconds-to-harvest").val(fieldData.time);
$("#delete-field-btn").show();
$("#save-field-btn").text( getLocalizedText("menu:save") );
let mapBlipCheckbox = $("#field-map-blip");
if(fieldData.blipSprite) {
$("#field-blip-name").val(fieldData.blipName);
$("#field-sprite-id").val(fieldData.blipSprite);
$("#field-blip-color").val(fieldData.blipColor);
$("#field-blip-scale").val(fieldData.blipScale);
mapBlipCheckbox.prop("checked", true);
} else {
mapBlipCheckbox.prop("checked", false);
}
mapBlipCheckbox.change();
$("#field-required-item-toggle").prop("checked", fieldData.requiredItem ? true : false).change();
$("#field-required-item-name").val(fieldData.requiredItem?.name || "");
$("#field-required-item-lose-on-use").prop("checked", fieldData.requiredItem?.loseOnUse ?? false);
$("#fields-items").empty();
if(fieldData.items) {
for(const itemData of fieldData.items) {
addItemToField(itemData);
}
}
fieldModal.modal("show");
}
$("#field-map-blip").change(function() {
let enabled = $(this).prop("checked");
$("#field-map-blip-inputs").find("input").prop("disabled", !enabled);
})
$("#field-form").submit(async function(event) {
if(isThereAnyErrorInForm(event)) return;
let fieldData = {
label: $("#field-label").val(),
radius: parseInt( $("#field-radius").val() ),
coords: {
x: parseFloat( $("#field-coords-x").val() ),
y: parseFloat( $("#field-coords-y").val() ),
z: parseFloat( $("#field-coords-z").val() )
},
items: getAllItemsFieldData(),
objectModel: $("#field-object-model").val(),
maxObjects: parseInt( $("#field-max-objects").val() ),
time: parseInt( $("#field-seconds-to-harvest").val() ),
requiredItem: $("#field-required-item-toggle").prop("checked") ? {
name: $("#field-required-item-name").val(),
loseOnUse: $("#field-required-item-lose-on-use").prop("checked")
} : undefined
}
let isBlipEnabled = $("#field-map-blip").prop("checked");
if( isBlipEnabled ) {
fieldData.blipName = $("#field-blip-name").val();
fieldData.blipSprite = parseInt( $("#field-sprite-id").val() );
fieldData.blipColor = parseInt( $("#field-blip-color").val() );
fieldData.blipScale = parseFloat( $("#field-blip-scale").val() );
}
let fieldModal = $("#field-modal");
let action = fieldModal.data("action");
let response = null;
switch(action) {
case "create": {
response = await $.post(`https://${resName}/createNewField`, JSON.stringify(fieldData))
break;
}
case "edit": {
const fieldId = fieldModal.data("fieldId");
response = await $.post(`https://${resName}/updateField`, JSON.stringify({fieldId: fieldId, fieldData: fieldData}));
break;
}
}
showServerResponse(response);
fieldModal.modal("hide");
loadDrugsFields()
})
$("#delete-field-btn").click(async function() {
if(!await confirmDeletion()) return;
let fieldId = $("#field-modal").data("fieldId");
const response = await $.post(`https://${resName}/deleteField`, JSON.stringify({fieldId: fieldId}))
showServerResponse(response);
$("#field-modal").modal("hide");
loadDrugsFields();
})
// Craftings creation
let craftingRecipesDatatable = $("#crafting-recipes-container").DataTable( {
"lengthMenu": [10, 15, 20],
"createdRow": function ( row, data, index ) {
$(row).addClass("clickable");
$(row).click(function() {
let craftingId = parseInt(data[0]);
editCraftingRecipe(craftingId);
})
},
} );
let craftingRecipes = {};
function loadCraftingRecipes() {
$.post(`https://${resName}/getAllCraftingRecipes`, {}, function(rawCraftingRecipes) {
// Manually create the table to avoid incompatibilities due table indexing
craftingRecipes = {};
for(const[k, craftingRecipe] of Object.entries(rawCraftingRecipes)) {
craftingRecipes[craftingRecipe.id] = craftingRecipe;
}
craftingRecipesDatatable.clear();
for(const[craftingId, craftingRecipe] of Object.entries(craftingRecipes)) {
craftingRecipesDatatable.row.add([
craftingId,
craftingRecipe.name,
craftingRecipe.resultItems.map(resultItem => `${resultItem.itemName}`).join(", "),
craftingRecipe.time
])
}
craftingRecipesDatatable.draw()
})
}
$("#new-crafting-btn").click(function() {
let craftingRecipeModal = $("#crafting-recipe-modal");
craftingRecipeModal.data("action", "create");
craftingRecipeModal.modal("show");
$("#crafting-recipe-result-items-container").empty();
$("#crafting-recipe-ingredients-container").empty();
$("#crafting-recipe-perfect-recipe-reward-container").empty();
// Adapts the modal to creation instead of edit
$("#crafting-recipe-modal-confirm-btn").text( getLocalizedText("menu:create") );
$("#delete-crafting-recipe-btn").hide();
$("#crafting-recipe-modal").find("input").val("");
// Adds one empty result item and ingredient
addResultItemInCraftingRecipe();
addIngredientInCraftingRecipe();
addRewardItemInCraftingRecipe();
});
// Adds a result item in the crafting recipe
function addResultItemInCraftingRecipe(itemName = "", itemQuantity = 1) {
let craftingRecipeContainer = $("#crafting-recipe-result-items-container");
let resultItemDiv = $(`
<div class="row g-2 mb-2 align-items-center result-item">
<div class="col-md">
<div class="form-floating">
<input type="text" class="form-control result-item-name" placeholder="Item name" value="${itemName}" required>
<label>${getLocalizedText("menu:item_name")}</label>
</div>
</div>
<button type="button" class="btn btn-secondary col-2 choose-item-btn">${getLocalizedText("menu:choose_item")}</button>
<div class="col-md ms-2">
<div class="form-floating">
<input type="number" class="form-control result-item-quantity" placeholder="Item quantity" value="${itemQuantity}" required>
<label>${getLocalizedText("menu:quantity")}</label>
</div>
</div>
<button type="button" class="btn-close remove-result-item-btn" aria-label="Close"></button>
</div>
`);
resultItemDiv.find(".choose-item-btn").click(async function() {
const itemName = await itemsDialog();
resultItemDiv.find(".result-item-name").val(itemName);
})
resultItemDiv.find(".remove-result-item-btn").click(function() {
resultItemDiv.remove();
})
craftingRecipeContainer.append(resultItemDiv);
}
$("#crafting-recipe-add-result-item-btn").click(function() {
addResultItemInCraftingRecipe();
})
function addIngredientInCraftingRecipe(ingredientName = "", ingredientMinQuantity = 1, ingredientMaxQuantity = 3, ingredientPerfectQuantity = 2, loseOnUse = true) {
let ingredientsContainer = $("#crafting-recipe-ingredients-container");
let ingredientDiv = $(`
<div class="d-flex g-2 gap-2 align-items-center mb-3 ingredient">
<div class="form-floating col">
<input type="text" class="form-control ingredient-item-name" placeholder="Item name" value="${ingredientName}" required>
<label>${getLocalizedText("menu:item_name")}</label>
</div>
<button type="button" class="btn btn-secondary col-auto choose-item-btn" data-bs-toggle="tooltip" data-bs-placement="top" title="${ getLocalizedText("menu:choose_item") }"><i class="bi bi-list-ul"></i></button>
<div class="form-check my-auto">
<input class="form-check-input" type="checkbox" ${loseOnUse ? "checked" : null}>
<label class="form-check-label">${ getLocalizedText("menu:lose_on_use") }</label>
</div>
<div class="form-floating col">
<input type="number" class="form-control ingredient-min-quantity" placeholder="Item name" value="${ingredientMinQuantity}" required>
<label>${getLocalizedText("menu:minimum_quantity")}</label>
</div>
<div class="form-floating col">
<input type="number" class="form-control ingredient-max-quantity" placeholder="Item name" value="${ingredientMaxQuantity}" required>
<label>${getLocalizedText("menu:maximum_quantity")}</label>
</div>
<div class="form-floating col">
<input type="number" class="form-control ingredient-perfect-quantity" placeholder="Item name" value="${ingredientPerfectQuantity}" required>
<label>${getLocalizedText("menu:perfect_quantity")}</label>
</div>
<button type="button" class="btn-close remove-ingredient-btn"></button>
</div>
<hr>
`);
ingredientDiv.find(".choose-item-btn").click(async function() {
const itemName = await itemsDialog();
ingredientDiv.find(".ingredient-item-name").val(itemName);
}).tooltip();
ingredientDiv.find(".remove-ingredient-btn").click(function() {
ingredientDiv.remove();
})
ingredientsContainer.append(ingredientDiv);
}
$("#crafting-recipe-add-ingredient-btn").click(function() {
addIngredientInCraftingRecipe();
})
// Get the data from crafting recipe modal
function getCraftingRecipeData() {
let resultItems = [];
$("#crafting-recipe-result-items-container").children(".result-item").each(function() {
let resultItem = {
itemName: $(this).find(".result-item-name").val(),
itemQuantity: parseInt( $(this).find(".result-item-quantity").val() )
}
resultItems.push(resultItem);
});
let ingredients = [];
$("#crafting-recipe-ingredients-container").children(".ingredient").each(function() {
let ingredient = {
ingredientItemName: $(this).find(".ingredient-item-name").val(),
ingredientMinQuantity: parseInt( $(this).find(".ingredient-min-quantity").val() ),
ingredientMaxQuantity: parseInt( $(this).find(".ingredient-max-quantity").val() ),
ingredientPerfectQuantity: parseInt( $(this).find(".ingredient-perfect-quantity").val() ),
loseOnUse: $(this).find(".form-check-input").is(":checked")
}
ingredients.push(ingredient);
});
let perfectRecipeReward = [];
$("#crafting-recipe-perfect-recipe-reward-container").children(".reward-item").each(function() {
let perfectRecipeRewardItem = {
itemName: $(this).find(".reward-item-name").val(),
itemQuantity: parseInt( $(this).find(".reward-item-quantity").val() )
}
perfectRecipeReward.push(perfectRecipeRewardItem);
});
let timeToCraft = parseInt( $("#crafting-recipe-time-to-craft").val() );
let craftName = $("#crafting-recipe-name").val();
return {
resultItems: resultItems,
ingredients: ingredients,
perfectRecipeReward: perfectRecipeReward,
time: timeToCraft,
name: craftName
}
}
// Saves the new crafting recipe created
$("#crafting-recipe-form").submit(async function(event) {
if(isThereAnyErrorInForm(event)) return;
let craftingRecipeData = getCraftingRecipeData();
let craftingRecipeModal = $("#crafting-recipe-modal");
let action = craftingRecipeModal.data("action");
let response = null;
switch(action) {
case "create": {
response = await $.post(`https://${resName}/createNewCraftingRecipe`, JSON.stringify(craftingRecipeData))
break;
}
case "edit": {
let craftingId = craftingRecipeModal.data("craftingId");
response = await $.post(`https://${resName}/updateCraftingRecipe`, JSON.stringify({craftingId: craftingId, craftingRecipe: craftingRecipeData}));
break;
}
}
showServerResponse(response);
craftingRecipeModal.modal("hide");
loadCraftingRecipes();
})
// To edit an existing crafting recipe
function editCraftingRecipe(craftingId) {
// Adapts the modal to edit instead of create
$("#delete-crafting-recipe-btn").show();
$("#crafting-recipe-modal-confirm-btn").text( getLocalizedText("menu:save") );
let craftingData = craftingRecipes[craftingId];
$("#crafting-recipe-time-to-craft").val(craftingData.time);
$("#crafting-recipe-name").val(craftingData.name);
$("#crafting-recipe-result-items-container").empty();
$("#crafting-recipe-ingredients-container").empty();
$("#crafting-recipe-perfect-recipe-reward-container").empty();
for (let resultItem of craftingData.resultItems) {
addResultItemInCraftingRecipe(resultItem.itemName, resultItem.itemQuantity);
}
for (let rewardItem of craftingData.perfectRecipeReward) {
addRewardItemInCraftingRecipe(rewardItem.itemName, rewardItem.itemQuantity);
}
for (let ingredient of craftingData.ingredients) {
addIngredientInCraftingRecipe(ingredient.ingredientItemName, ingredient.ingredientMinQuantity, ingredient.ingredientMaxQuantity, ingredient.ingredientPerfectQuantity, ingredient.loseOnUse);
}
let craftingRecipeModal = $("#crafting-recipe-modal");
craftingRecipeModal.modal("show");
craftingRecipeModal.data("action", "edit");
craftingRecipeModal.data("craftingId", craftingId);
}
// Deletes crafting recipe
$("#delete-crafting-recipe-btn").click(async function() {
if(!await confirmDeletion()) return;
let craftingId = $("#crafting-recipe-modal").data("craftingId");
const response = await $.post(`https://${resName}/deleteCraftingRecipe`, JSON.stringify({craftingId: craftingId}));
showServerResponse(response);
$("#crafting-recipe-modal").modal("hide");
loadCraftingRecipes();
})
// Adds a reward item in the crafting recipe for perfect crafting
function addRewardItemInCraftingRecipe(itemName = "", itemQuantity = 1) {
let rewardsContainer = $("#crafting-recipe-perfect-recipe-reward-container");
let rewardItemDiv = $(`
<div class="row g-2 mb-2 align-items-center reward-item">
<div class="col-md">
<div class="form-floating">
<input type="text" class="form-control reward-item-name" placeholder="Item name" value="${itemName}" required>
<label>${getLocalizedText("menu:item_name")}</label>
</div>
</div>
<button type="button" class="btn btn-secondary col-2 choose-item-btn">${getLocalizedText("menu:choose_item")}</button>
<div class="col-md ms-2">
<div class="form-floating">
<input type="number" class="form-control reward-item-quantity" placeholder="Item quantity" value="${itemQuantity}" required>
<label>${getLocalizedText("menu:quantity")}</label>
</div>
</div>
<button type="button" class="btn-close remove-reward-item-btn" aria-label="Close"></button>
</div>
`);
rewardItemDiv.find(".choose-item-btn").click(async function() {
const itemName = await itemsDialog();
rewardItemDiv.find(".reward-item-name").val(itemName);
})
rewardItemDiv.find(".remove-reward-item-btn").click(function() {
rewardItemDiv.remove();
})
rewardsContainer.append(rewardItemDiv);
}
$("#crafting-recipe-add-reward-item-btn").click(function() {
addRewardItemInCraftingRecipe();
})
// Laboratories
let laboratoriesDatatable = $("#laboratories-container").DataTable( {
"lengthMenu": [10, 15, 20],
"createdRow": function ( row, data, index ) {
$(row).addClass("clickable");
$(row).click(function() {
let laboratoryId = parseInt(data[0]);
editLaboratory(laboratoryId);
})
},
} );
let laboratories = {};
function loadLaboratories() {
$.post(`https://${resName}/getAllLaboratories`, {}, function(rawLaboratories) {
// Manually create the table to avoid incompatibilities due table indexing
laboratories = {};
for(const[k, laboratoryData] of Object.entries(rawLaboratories)) {
laboratories[laboratoryData.id] = laboratoryData;
}
laboratoriesDatatable.clear();
for(const[laboratoryId, laboratoryData] of Object.entries(laboratories)) {
laboratoriesDatatable.row.add([
laboratoryId,
laboratoryData.name,
laboratoryData.coords.x,
laboratoryData.coords.y,
laboratoryData.coords.z,
])
}
laboratoriesDatatable.draw()
})
}
// Create new laboratory
$("#new-laboratory-btn").click(function() {
let laboratoryModal = $("#laboratory-modal");
laboratoryModal.modal("show");
laboratoryModal.data("action", "create");
$("#laboratory-name").val("");
$("#laboratory-coords-x").val("");
$("#laboratory-coords-y").val("");
$("#laboratory-coords-z").val("");
$("#laboratory-scale-x").val("1.5");
$("#laboratory-scale-y").val("1.5");
$("#laboratory-scale-z").val("0.5");
$("#laboratory-icon-type").val("1");
$("#laboratory-opacity").val("100");
$("#laboratory-map-blip").prop("checked", false).change();
// Resets stuff related to allowed jobs (all allowed)
setLaboratoryAllowedJobsText(false);
$("#laboratory-allowed-jobs").data("jobs", false);
// Resets stuff related to allowed recipes
$("#laboratory-allowed-crafting-recipes").text(getLocalizedText("menu:no_crafting_recipe_allowed"));
$("#laboratory-allowed-crafting-recipes").data("craftingRecipes", {});
// Adapts the modal from edit mode to create mode
$("#delete-laboratory-btn").hide();
$("#laboratory-modal-confirm-btn").text( getLocalizedText("menu:create") );
})
function setLaboratoryAllowedJobsText(jobs) {
if(jobs === false) {
$("#laboratory-allowed-jobs").text(getLocalizedText("menu:all_jobs_allowed"));
} else if(Object.keys(jobs).length == 0) {
$("#laboratory-allowed-jobs").text(getLocalizedText("menu:no_job_allowed"));
} else {
$("#laboratory-allowed-jobs").text( Object.keys(jobs).join(", ") );
}
}
$("#laboratory-choose-jobs").click(async function() {
const oldJobs = $("#laboratory-allowed-jobs").data("jobs");
const jobs = await jobsDialog(oldJobs);
setLaboratoryAllowedJobsText(jobs);
$("#laboratory-allowed-jobs").data("jobs", jobs)
})
// Dialog that shows all crafting recipes and returns the selected ones
function craftingRecipesDialog(cb) {
let inputCraftingRecipesModal = $("#input-crafting-recipes-dialog-modal")
inputCraftingRecipesModal.modal("show");
$("#input-crafting-recipes-search").val("");
$.post(`https://${resName}/getAllCraftingRecipes`, JSON.stringify({}), function (craftingRecipes) {
let craftingRecipesListDiv = $("#crafting-recipes-list");
craftingRecipesListDiv.empty();
for(const[k, craftingRecipeData] of Object.entries(craftingRecipes)) {
let craftingRecipeDiv = $(`
<div class="form-check fs-3">
<input class="form-check-input" type="checkbox" value="${craftingRecipeData.id}">
<label class="form-check-label">
${craftingRecipeData.name}
</label>
</div>
`);
craftingRecipesListDiv.append(craftingRecipeDiv);
}
// Unbinds the button and rebinds it to callback the selected crafting recipes
$("#input-crafting-recipes-confirm-btn").unbind().click(function() {
let selectedCraftingRecipes = {};
craftingRecipesListDiv.find("input:checked").each(function() {
let recipeId = $(this).val();
selectedCraftingRecipes[recipeId] = true;
});
inputCraftingRecipesModal.modal("hide");
cb(selectedCraftingRecipes);
})
});
}
$("#input-crafting-recipe-search").on("keyup", function() {
let text = $(this).val().toLowerCase();
$("#crafting-recipes-list .form-check").filter(function() {
$(this).toggle($(this).text().toLowerCase().indexOf(text) > -1)
});
})
// Choose crafting recipes for laboratory
$("#laboratory-choose-recipes").click(function() {
craftingRecipesDialog((selectedRecipes) => {
if(Object.keys(selectedRecipes).length == 0) {
$("#laboratory-allowed-crafting-recipes").text(getLocalizedText("menu:no_crafting_recipe_allowed"));
} else {
$("#laboratory-allowed-crafting-recipes").text(Object.keys(selectedRecipes).map(recipeId => craftingRecipes[recipeId].name).join(", "));
}
$("#laboratory-allowed-crafting-recipes").data("recipes", selectedRecipes);
})
})
// Sets current coords for laboratory
$("#laboratory-current-coords-btn").click(async function(event) {
if(event.shiftKey) {
const coordsInUI = {
x: parseFloat( $("#laboratory-coords-x").val() ),
y: parseFloat( $("#laboratory-coords-y").val() ),
z: parseFloat( $("#laboratory-coords-z").val() )
}
teleportToCoords(coordsInUI);
return;
}
const coords = await getCurrentCoords();
$("#laboratory-coords-x").val(coords.x);
$("#laboratory-coords-y").val(coords.y);
$("#laboratory-coords-z").val(coords.z);
})
// To edit an existing laboratory
function editLaboratory(laboratoryId) {
let laboratoryModal = $("#laboratory-modal");
laboratoryModal.data("laboratoryId", laboratoryId);
laboratoryModal.data("action", "edit");
let laboratoryData = laboratories[laboratoryId];
$("#laboratory-name").val(laboratoryData.name);
$("#laboratory-coords-x").val(laboratoryData.coords.x);
$("#laboratory-coords-y").val(laboratoryData.coords.y);
$("#laboratory-coords-z").val(laboratoryData.coords.z);
$("#laboratory-scale-x").val(laboratoryData.scale.x);
$("#laboratory-scale-y").val(laboratoryData.scale.y);
$("#laboratory-scale-z").val(laboratoryData.scale.z);
$("#laboratory-bounce").prop("checked", laboratoryData.bounce);
$("#laboratory-follow-camera").prop("checked", laboratoryData.followCamera);
$("#laboratory-rotate").prop("checked", laboratoryData.rotate);
$("#laboratory-icon-type").val(laboratoryData.iconType);
$("#laboratory-color").val( rgbToHex(laboratoryData.color.r, laboratoryData.color.g, laboratoryData.color.b) );
$("#laboratory-opacity").val(laboratoryData.opacity);
let mapBlipCheckbox = $("#laboratory-map-blip");
if(laboratoryData.blipSprite) {
$("#laboratory-blip-name").val(laboratoryData.blipName);
$("#laboratory-sprite-id").val(laboratoryData.blipSprite);
$("#laboratory-blip-color").val(laboratoryData.blipColor);
$("#laboratory-blip-scale").val(laboratoryData.blipScale);
mapBlipCheckbox.prop("checked", true);
} else {
mapBlipCheckbox.prop("checked", false);
}
mapBlipCheckbox.change();
setLaboratoryAllowedJobsText(laboratoryData.allowedJobs);
$("#laboratory-allowed-jobs").data("jobs", laboratoryData.allowedJobs);
let sanitizedAllowedRecipes = {};
Object.keys(laboratoryData.allowedRecipes).forEach(recipeId => {
if(recipeId == 0) {
recipeId = 1;
}
sanitizedAllowedRecipes[recipeId] = true;
});
$("#laboratory-allowed-crafting-recipes").text( Object.keys(sanitizedAllowedRecipes).map(recipeId => craftingRecipes[recipeId] ? craftingRecipes[recipeId].name : null).join(", ") );
$("#laboratory-allowed-crafting-recipes").data("recipes", sanitizedAllowedRecipes);
// Adatps the modal from create mode to edit mode
$("#delete-laboratory-btn").show();
$("#laboratory-modal-confirm-btn").text( getLocalizedText("menu:save") );
laboratoryModal.modal("show");
}
// To delete an existing laboratory
$("#delete-laboratory-btn").click(async function() {
if(!await confirmDeletion()) return;
let laboratoryId = $("#laboratory-modal").data("laboratoryId");
if(laboratoryId == null) return;
const response = await $.post(`https://${resName}/deleteLaboratory`, JSON.stringify({
laboratoryId: laboratoryId
}));
showServerResponse(response);
$("#laboratory-modal").modal("hide");
loadLaboratories();
})
$("#laboratory-map-blip").change(function() {
let enabled = $(this).prop("checked");
$("#laboratory-map-blip-inputs").find("input").prop("disabled", !enabled);
})
// Gets the laboratory data from its modal
function getLaboratoryData() {
let laboratoryData = {
name: $("#laboratory-name").val(),
coords: {
x: parseFloat( $("#laboratory-coords-x").val() ),
y: parseFloat( $("#laboratory-coords-y").val() ),
z: parseFloat( $("#laboratory-coords-z").val() )
},
iconType: parseInt( $("#laboratory-icon-type").val() ),
scale: {
x: parseFloat( $("#laboratory-scale-x").val() ),
y: parseFloat( $("#laboratory-scale-y").val() ),
z: parseFloat( $("#laboratory-scale-z").val() ),
},
bounce: $("#laboratory-bounce").prop("checked"),
followCamera: $("#laboratory-follow-camera").prop("checked"),
rotate: $("#laboratory-rotate").prop("checked"),
color: hexToRgb( $("#laboratory-color").val() ),
opacity: parseInt( $("#laboratory-opacity").val() ),
allowedJobs: $("#laboratory-allowed-jobs").data("jobs"),
allowedRecipes: $("#laboratory-allowed-crafting-recipes").data("recipes") || {}
}
let isBlipEnabled = $("#laboratory-map-blip").prop("checked");
if( isBlipEnabled ) {
laboratoryData.blipName = $("#laboratory-blip-name").val();
laboratoryData.blipSprite = parseInt( $("#laboratory-sprite-id").val() );
laboratoryData.blipColor = parseInt( $("#laboratory-blip-color").val() );
laboratoryData.blipScale = parseFloat( $("#laboratory-blip-scale").val() );
}
return laboratoryData;
}
// Saves laboratory data
$("#laboratory-form").submit(async function(event) {
if(isThereAnyErrorInForm(event)) return;
let laboratoryModal = $("#laboratory-modal");
let laboratoryData = getLaboratoryData();
let action = laboratoryModal.data("action");
let response = null;
switch(action) {
case "create": {
response = await $.post(`https://${resName}/createNewLaboratory`, JSON.stringify(laboratoryData))
break;
}
case "edit": {
let laboratoryId = laboratoryModal.data("laboratoryId");
response = await $.post(`https://${resName}/updateLaboratory`, JSON.stringify({laboratoryId: laboratoryId, laboratoryData: laboratoryData}))
break;
}
}
showServerResponse(response);
laboratoryModal.modal("hide");
loadLaboratories();
})
function addRequiredItemInPocketCrafting(pocketCraftingDiv, itemName = "", itemQuantity = 1, loseOnUse = true) {
let requiredItemsContainer = pocketCraftingDiv.find(".pocket-crafting-required-items");
let requiredItemDiv = $(`
<div class="d-flex justify-content-center align-items-center gap-3 mb-2 required-item">
<div class="form-floating col-3">
<input type="text" class="form-control required-item-name" value="${itemName}" required>
<label>${getLocalizedText("menu:item_name")}</label>
</div>
<button type="button" class="btn btn-secondary col-auto choose-item-btn me-3" data-bs-toggle="tooltip" data-bs-placement="top" title="${ getLocalizedText("menu:choose_item") }"><i class="bi bi-list-ul"></i></button>
<div class="form-check my-auto me-3">
<input class="form-check-input" type="checkbox" ${loseOnUse ? "checked" : null}>
<label class="form-check-label">${ getLocalizedText("menu:lose_on_use") }</label>
</div>
<div class="form-floating col-3">
<input type="number" class="form-control required-item-quantity" value="${itemQuantity}" required>
<label>${getLocalizedText("menu:quantity")}</label>
</div>
<button type="button" class="btn-close remove-required-item-btn"></button>
</div>
`);
requiredItemDiv.find(".choose-item-btn").click(async function() {
const itemName = await itemsDialog();
requiredItemDiv.find(".required-item-name").val(itemName);
}).tooltip();
requiredItemDiv.find(".remove-required-item-btn").click(function() {
requiredItemDiv.remove();
});
requiredItemsContainer.append(requiredItemDiv);
}
async function addPocketCrafting(itemName, pocketCraftingData, isNew = false) {
const itemLabel = await getItemLabel(itemName) || "Unknown item - " + itemName;
const div = $(`
<div class="pocket-crafting mb-4" data-item-to-use="${itemName}">
<h2 class="text-center title">${getLocalizedText("menu:you_have_to_use")} <span class="text-success">'${itemLabel}'</span> ${getLocalizedText("menu:to_start_the_crafting")} <span class="fs-5 fw-lighter fst-italic">(${itemName})</span></h2>
<div class="d-flex justify-content-center align-items-center gap-3 mb-5">
<h3 class="my-auto">${getLocalizedText("menu:to_receive")}</h3>
<div class="form-floating col-3">
<input type="text" class="form-control result-item-name" required>
<label>${getLocalizedText("menu:pocket_craftings:result_item")}</label>
</div>
<button type="button" class="btn btn-secondary col-auto choose-item-btn me-5" data-bs-toggle="tooltip" data-bs-placement="top" title="${ getLocalizedText("menu:choose_item") }"><i class="bi bi-list-ul"></i></button>
<div class="form-floating col-3">
<input type="number" min="1" class="form-control time-to-craft" required>
<label>${getLocalizedText("menu:crafting_recipe:time_to_craft")}</label>
</div>
<div class="form-floating col-3">
<input type="number" min="1" class="form-control item-quantity" required>
<label>${getLocalizedText("menu:quantity")}</label>
</div>
</div>
<h3 class="text-center">${getLocalizedText("menu:pocket_craftings:required_items")}</h3>
<div class="pocket-crafting-required-items">
</div>
<div class="d-inline-block col-12 my-2">
<button type="button" class="btn float-start btn-danger delete-pocket-crafting">${getLocalizedText("menu:pocket_craftings:delete_pocket_crafting")}</button>
<button type="button" class="btn float-end btn-secondary add-required-item-btn">${getLocalizedText("menu:pocket_craftings:add_required_item")}</button>
</div>
</div>
<hr>
`);
div.find(".choose-item-btn").click(async function() {
const itemName = await itemsDialog();
div.find(".result-item-name").val(itemName);
}).tooltip();
div.find(".add-required-item-btn").click(function() {
addRequiredItemInPocketCrafting(div);
});
div.find(".delete-pocket-crafting").click(function() {
div.remove();
});
if(pocketCraftingData) {
div.find(".result-item-name").val(pocketCraftingData.resultItem.name);
div.find(".item-quantity").val(pocketCraftingData.resultItem.quantity);
div.find(".time-to-craft").val(pocketCraftingData.timeToCraft);
for(const requiredItem of pocketCraftingData.requiredItems) {
addRequiredItemInPocketCrafting(div, requiredItem.name, requiredItem.quantity, requiredItem.loseOnUse);
}
}
if(isNew) {
div.find(".title").append(`<span class="fs-5 fw-lighter fst-italic text-danger"> - ${ getLocalizedText("menu:effects:may_need_restart") }</span>`)
}
$("#pocket-craftings-list").append(div);
}
// Pocket sellings
$("#create-new-pocket-crafting-btn").click(async function() {
const itemName = await itemsDialog(getLocalizedText("menu:choose_the_item_that_will_start_pocket_crafting_on_use"));
if(!itemName) return;
addPocketCrafting(itemName, null, true);
});
function getAllPocketCraftings() {
const pocketCraftings = {};
$("#pocket-craftings-list").find(".pocket-crafting").each(function() {
const itemName = $(this).data("itemToUse");
const pocketCrafting = {
resultItem: {
name: $(this).find(".result-item-name").val(),
quantity: parseInt($(this).find(".item-quantity").val())
},
timeToCraft: parseInt($(this).find(".time-to-craft").val()),
requiredItems: []
};
$(this).find(".required-item").each(function() {
const requiredItem = {
name: $(this).find(".required-item-name").val(),
quantity: parseInt($(this).find(".required-item-quantity").val()),
loseOnUse: $(this).find(".form-check-input").prop("checked")
};
pocketCrafting.requiredItems.push(requiredItem);
});
pocketCraftings[itemName] = pocketCrafting;
});
return pocketCraftings;
}
$("#pocket-craftings").submit(async function(event) {
if(isThereAnyErrorInForm(event)) return;
let serverSettings = {
pocketCraftings: getAllPocketCraftings(),
}
const response = await $.post(`https://${resName}/saveSettings`, JSON.stringify({
clientSettings: {},
serverSettings: serverSettings,
sharedSettings: {}
}));
showServerResponse(response);
});
// Plane selling
$("#plane-selling-current-coords-btn").click(async function(event) {
if(event.shiftKey) {
const coordsInUI = {
x: parseFloat( $("#plane-selling-coords-x").val() ),
y: parseFloat( $("#plane-selling-coords-y").val() ),
z: parseFloat( $("#plane-selling-coords-z").val() )
}
teleportToCoords(coordsInUI);
return;
}
const coords = await getCurrentCoords();
$("#plane-selling-coords-x").val(coords.x);
$("#plane-selling-coords-y").val(coords.y);
$("#plane-selling-coords-z").val(coords.z);
})
function togglePlaneWholeOcean() {
let enabled = $("#plane-use-the-whole-ocean").prop("checked");
$("#plane-selling-coords").find("input").prop("disabled", enabled);
}
$("#plane-use-the-whole-ocean").change(togglePlaneWholeOcean);
$("#plane-add-new-drug").click(function() {
addAcceptableDrugToDiv("#plane-selling-acceptable-drugs");
})
// Enables/Disables all inputs for plane selling
function togglePlaneSelling() {
let enable = $("#enable-plane-selling").prop("checked");
$("#selling-plane").find("input, button").not("#enable-plane-selling").prop("disabled", !enable);
}
$("#enable-plane-selling").change(togglePlaneSelling);
// Boat selling
$("#boat-selling-current-coords-btn").click(async function(event) {
if(event.shiftKey) {
const coordsInUI = {
x: parseFloat( $("#boat-selling-coords-x").val() ),
y: parseFloat( $("#boat-selling-coords-y").val() ),
z: parseFloat( $("#boat-selling-coords-z").val() )
}
teleportToCoords(coordsInUI);
return;
}
const coords = await getCurrentCoords();
$("#boat-selling-coords-x").val(coords.x);
$("#boat-selling-coords-y").val(coords.y);
$("#boat-selling-coords-z").val(coords.z);
});
function toggleBoatWholeOcean() {
let enabled = $("#boat-use-the-whole-ocean").prop("checked");
$("#boat-selling-coords").find("input").prop("disabled", enabled);
}
$("#boat-use-the-whole-ocean").change(toggleBoatWholeOcean);
$("#boat-add-new-drug").click(function() {
addAcceptableDrugToDiv("#boat-selling-acceptable-drugs");
})
function toggleBoatSelling() {
let enable = $("#enable-boat-selling").prop("checked");
$("#selling-boat").find("input, button").not("#enable-boat-selling").prop("disabled", !enable);
}
$("#enable-boat-selling").change(toggleBoatSelling);
// NPC Selling
function toggleNpcSelling() {
let enable = $("#enable-npc-selling").prop("checked");
$("#selling-npc").find("input, button").not("#enable-npc-selling").prop("disabled", !enable);
}
$("#enable-npc-selling").change(toggleNpcSelling);
function addAcceptableDrugToDiv(drugsListDiv, drugName = "", minPrice = 500, maxPrice = 1000) {
let drugDiv = $(`
<div class="d-flex gap-3 align-items-center justify-content-center mt-2 mb-3 drug">
<div class="form-floating col-4">
<input type="text" class="form-control drug-name" placeholder="Name" required value=${drugName}>
<label>Drug name</label>
</div>
<button type="button" class="btn btn-secondary col-auto choose-item-btn me-5" data-bs-toggle="tooltip" data-bs-placement="top" title="${ getLocalizedText("menu:choose_item") }"><i class="bi bi-list-ul"></i></button>
<div class="form-floating">
<input type="number" class="form-control drug-min-price" placeholder="Max quantity" required value=${minPrice}>
<label>${ getLocalizedText("menu:min_price") }</label>
</div>
<div class="form-floating">
<input type="number" class="form-control drug-max-price" placeholder="Max quantity" required value=${maxPrice}>
<label>${ getLocalizedText("menu:max_price") }</label>
</div>
<button type="button" class="btn-close btn-close-white remove-btn ms-3"></button>
</div>
`)
drugDiv.find(".choose-item-btn").click(async function() {
const itemName = await itemsDialog();
drugDiv.find(".drug-name").val(itemName);
}).tooltip();
drugDiv.find(".remove-btn").click(function() {
drugDiv.remove();
});
$(drugsListDiv).append(drugDiv);
}
function fillAcceptableDrugsForDiv(drugsListDiv, drugs) {
$(drugsListDiv).empty();
if(drugs) {
for(drug of drugs) {
addAcceptableDrugToDiv(drugsListDiv, drug.name, drug.minPrice, drug.maxPrice);
}
}
}
function getAcceptableDrugFromDiv(drugsListDiv) {
let drugs = [];
$(drugsListDiv).find(".drug").each(function() {
let drug = {
name: $(this).find(".drug-name").val(),
minPrice: parseInt( $(this).find(".drug-min-price").val() ),
maxPrice: parseInt( $(this).find(".drug-max-price").val() ),
}
drugs.push(drug);
})
return drugs;
}
$("#npc-add-new-drug").click(function() {
addAcceptableDrugToDiv("#npc-selling-acceptable-drugs");
})
$("#npc-selling-command-is-enabled").change(function() {
const enabled = $(this).prop("checked");
$("#npc-selling-command").prop("disabled", !enabled);
$("#npc-selling-command-has-to-spawn-npc").prop("disabled", !enabled);
});
// Save selling
$("#selling").submit(async function(event) {
if(isThereAnyErrorInForm(event)) return;
let clientSettings = {
// Boat and plane selling
heightToSell: parseInt( $("#minimum-plane-height").val() ),
sellUseWholeOcean: {
plane: $("#plane-use-the-whole-ocean").prop("checked"),
boat: $("#boat-use-the-whole-ocean").prop("checked")
},
sellArea: {
plane: {
coords: {
x: parseFloat( $("#plane-selling-coords-x").val() ),
y: parseFloat( $("#plane-selling-coords-y").val() ),
z: parseFloat( $("#plane-selling-coords-z").val() )
},
radius: parseFloat( $("#plane-selling-coords-radius").val() )
},
boat: {
coords: {
x: parseFloat( $("#boat-selling-coords-x").val() ),
y: parseFloat( $("#boat-selling-coords-y").val() ),
z: parseFloat( $("#boat-selling-coords-z").val() )
},
radius: parseFloat( $("#boat-selling-coords-radius").val() )
}
},
sellShowRadius: {
plane: $("#plane-show-radius-while-selling").prop("checked"),
boat: $("#boat-show-radius-while-selling").prop("checked")
},
// Narcos selling
narcosModel: $("#narcos-model").val(),
showNarcosBlip: $("#enable-narcos-blip").prop("checked"),
narcosBlip: {
name: $("#narcos-selling-blip-name").val(),
color: parseInt( $("#narcos-selling-blip-color").val() ),
scale: parseFloat( $("#narcos-selling-blip-scale").val() ),
sprite: parseInt( $("#narcos-selling-blip-sprite").val() )
},
// Pushers selling
pusherModel: $("#pushers-model").val(),
showPushersBlips: $("#enable-pushers-blip").prop("checked"),
pusherBlip: {
name: $("#pushers-selling-blip-name").val(),
color: parseInt( $("#pushers-selling-blip-color").val() ),
scale: parseFloat( $("#pushers-selling-blip-scale").val() ),
sprite: parseInt( $("#pushers-selling-blip-sprite").val() )
}
}
let serverSettings = {
rewards: {
plane: getRewardDivData("#plane-selling-reward-div"),
boat: getRewardDivData("#boat-selling-reward-div"),
npc: getRewardDivData("#npc-selling-reward-div"),
narcos: getRewardDivData("#narcos-selling-reward-div"),
pushers: getRewardDivData("#pushers-selling-reward-div")
},
// Plane selling
planeAcceptableDrugs: getAcceptableDrugFromDiv("#plane-selling-acceptable-drugs"),
planeSellingMinimumPolice: parseInt( $("#plane-minimum-police").val() ),
// Boat selling
boatAcceptableDrugs: getAcceptableDrugFromDiv("#boat-selling-acceptable-drugs"),
boatSellingMinimumPolice: parseInt( $("#boat-minimum-police").val() ),
// NPC selling
minNPCSellQuantity: parseInt( $("#npc-selling-min-quantity").val() ),
maxNPCSellQuantity: parseInt( $("#npc-selling-max-quantity").val() ),
sellToNPCChancesToAccept: parseInt( $("#npc-accept-chances").val() ),
maxNPCsSellableDrugQuantity: parseInt( $("#npc-max-drug-quantity").val() ),
npcAcceptableDrugs: getAcceptableDrugFromDiv("#npc-selling-acceptable-drugs"),
npcSellingMinimumPolice: parseInt( $("#npc-minimum-police").val() ),
npcAlertPoliceChances: parseInt( $("#npc-alert-police-chances").val() ),
canNPCRobPlayer: $("#npc-can-rob-player").prop("checked"),
canNPCAttackPlayer: $("#npc-can-attack-player").prop("checked"),
// Narcos selling
narcosBuyerLocations: getNarcosBuyerLocations(),
narcosLocationChangeTime: parseInt( $("#narcos-location-change").val() ),
narcosCallPoliceChances: parseInt( $("#narcos-police-alert-chances").val() ),
narcosAcceptsOnlyOneBuyerPerLocation: $("#narcos-accepts-only-one-buyer-per-location").prop("checked"),
narcosNeededDrugs: getNarcosDrugs(),
narcosSellingMinimumPolice: parseInt( $("#narcos-minimum-police").val() ),
// Pushers selling
pushersCallPoliceChances: parseInt( $("#pushers-alert-police-chances").val() ),
pushers: getPushers(),
pushersSellingMinimumPolice: parseInt( $("#pushers-minimum-police").val() ),
}
let sharedSettings = {
enableAirplaneSell: $("#enable-plane-selling").prop("checked"),
enableBoatSell: $("#enable-boat-selling").prop("checked"),
timeToSellInPlane: parseInt( $("#time-to-sell-in-plane").val() ),
timeToSellInBoat: parseInt( $("#time-to-sell-in-boat").val() ),
alarmPoliceInPlane: $("#enable-plane-police-alert").prop("checked"),
alarmPoliceInBoat: $("#enable-boat-police-alert").prop("checked"),
// NPC selling
npcSecondsToSell: parseInt( $("#npc-seconds-to-sell").val() ),
// Pushers selling
arePushersEnabled: $("#enable-pushers-selling").prop("checked"),
// Narcos selling
enableNarcosSelling: $("#enable-narcos-selling").prop("checked"),
enableNPCSell: $("#enable-npc-selling").prop("checked"),
}
const response = await $.post(`https://${resName}/saveSettings`, JSON.stringify({
clientSettings: clientSettings,
serverSettings: serverSettings,
sharedSettings: sharedSettings
}));
showServerResponse(response);
})
// Narcos selling
function toggleNarcosSelling() {
let narcosSelling = $("#enable-narcos-selling").prop("checked");
$("#selling-narcos").find("input, button").not("#enable-narcos-selling").prop("disabled", !narcosSelling);
}
$("#enable-narcos-selling").change(toggleNarcosSelling);
function toggleNarcosBlip() {
let narcosBlip = $("#enable-narcos-blip").prop("checked");
$("#narcos-selling-blip-name").prop("disabled", !narcosBlip);
$("#narcos-selling-blip-color").prop("disabled", !narcosBlip);
$("#narcos-selling-blip-scale").prop("disabled", !narcosBlip);
$("#narcos-selling-blip-sprite").prop("disabled", !narcosBlip);
}
$("#enable-narcos-blip").change(toggleNarcosBlip);
function addNarcosSpawnLocation(coords = {}, heading = 0.0) {
let newSpawnDiv = $(`
<div class="mb-5 spawn-location">
<div class="row g-2 row-cols-auto align-items-center text-body my-2">
<div class="form-floating col-3">
<input type="number" step="0.01" class="form-control max-two-decimals coord-x" placeholder="X" required value=${coords.x || ""}>
<label>${ getLocalizedText("menu:x") }</label>
</div>
<div class="form-floating col-3">
<input type="number" step="0.01" class="form-control max-two-decimals coord-y" placeholder="Y" required value=${coords.y || ""}>
<label>${ getLocalizedText("menu:y") }</label>
</div>
<div class="form-floating col-3">
<input type="number" step="0.01" class="form-control max-two-decimals coord-z" placeholder="Z" required value=${coords.z || ""}>
<label>${ getLocalizedText("menu:z") }</label>
</div>
<div class="form-floating col-2">
<input type="number" step="0.01" class="form-control max-two-decimals heading" placeholder="Heading" required value=${heading}>
<label>${ getLocalizedText("menu:heading") }</label>
</div>
<button type="button" class="btn-close btn-close-white ms-3 remove-btn"></button>
</div>
<button type="button" class="btn btn-secondary current-coords-btn">${ getLocalizedText("menu:current_coords_heading") }</button>
</div>
`)
newSpawnDiv.find(".max-two-decimals").on("change", maxTwoDecimals);
newSpawnDiv.find(".current-coords-btn").click(async function(event) {
if(event.shiftKey) {
const coordsInUI = {
x: parseFloat( newSpawnDiv.find(".coord-x").val() ),
y: parseFloat( newSpawnDiv.find(".coord-y").val() ),
z: parseFloat( newSpawnDiv.find(".coord-z").val() )
}
teleportToCoords(coordsInUI, parseFloat(newSpawnDiv.find(".heading").val()));
return;
}
const data = await getCurrentCoordsAndHeading();
newSpawnDiv.find(".coord-x").val(data.coords.x);
newSpawnDiv.find(".coord-y").val(data.coords.y);
newSpawnDiv.find(".coord-z").val(data.coords.z);
newSpawnDiv.find(".heading").val(data.heading);
});
newSpawnDiv.find(".remove-btn").click(function() {
newSpawnDiv.remove();
});
$("#narcos-spawn-locations").append(newSpawnDiv);
}
$("#narcos-add-spawn-btn").click(function() {
addNarcosSpawnLocation();
})
function getNarcosBuyerLocations() {
let buyerLocations = [];
$("#narcos-spawn-locations").find(".spawn-location").each(function() {
let spawnDiv = $(this);
let coords = {
x: parseFloat( spawnDiv.find(".coord-x").val() ),
y: parseFloat( spawnDiv.find(".coord-y").val() ),
z: parseFloat( spawnDiv.find(".coord-z").val() )
};
let heading = parseFloat( spawnDiv.find(".heading").val() );
buyerLocations.push({
coords: coords,
heading: heading
});
})
return buyerLocations;
}
function addNarcosDrug(drugName = "", minQuantity = 1, maxQuantity = 2, minPrice = 500, maxPrice = 1000) {
let drugDiv = $(`
<div class="d-flex gap-3 justify-content-center align-items-center mt-2 mb-3 drug">
<div class="form-floating">
<input type="text" class="form-control drug-name" placeholder="Name" required value=${drugName}>
<label>Drug name</label>
</div>
<button type="button" class="btn btn-secondary col-auto choose-item-btn" data-bs-toggle="tooltip" data-bs-placement="top" title="${ getLocalizedText("menu:choose_item") }"><i class="bi bi-list-ul"></i></button>
<div class="form-floating col-2">
<input type="number" class="form-control drug-min-quantity" placeholder="Min quantity" required value=${minQuantity}>
<label>Min quantity</label>
</div>
<div class="form-floating col-2">
<input type="number" class="form-control drug-max-quantity" placeholder="Max quantity" required value=${maxQuantity}>
<label>Max quantity</label>
</div>
<div class="form-floating col-2">
<input type="number" class="form-control drug-min-price" placeholder="Max quantity" required value=${minPrice}>
<label>${ getLocalizedText("menu:min_price") }</label>
</div>
<div class="form-floating col-2">
<input type="number" class="form-control drug-max-price" placeholder="Max quantity" required value=${maxPrice}>
<label>${ getLocalizedText("menu:max_price") }</label>
</div>
<button type="button" class="btn-close btn-close-white remove-btn ms-2"></button>
</div>
`)
drugDiv.find(".choose-item-btn").click(async function() {
const itemName = await itemsDialog();
drugDiv.find(".drug-name").val(itemName);
}).tooltip();
drugDiv.find(".remove-btn").click(function() {
drugDiv.remove();
});
$("#narcos-selling-acceptable-drugs").append(drugDiv);
}
$("#narcos-add-new-drug").click(function() {
addNarcosDrug();
})
function getNarcosDrugs() {
let drugs = [];
$("#narcos-selling-acceptable-drugs").find(".drug").each(function() {
let drug = {
name: $(this).find(".drug-name").val(),
minPrice: parseInt( $(this).find(".drug-min-price").val() ),
maxPrice: parseInt( $(this).find(".drug-max-price").val() ),
minQuantity: parseInt( $(this).find(".drug-min-quantity").val() ),
maxQuantity: parseInt( $(this).find(".drug-max-quantity").val() )
}
drugs.push(drug);
})
return drugs;
}
// Pushers
function togglePushersSelling() {
let enabled = $("#enable-pushers-selling").prop("checked");
$("#selling-pushers").find("input, button").not("#enable-pushers-selling").prop("disabled", !enabled);
}
$("#enable-pushers-selling").change(togglePushersSelling);
function addPusherDrug(pusherDiv, drugData = {}) {
let drugDiv = $(`
<div class="d-flex justify-content-center align-items-center gap-3 mb-6 pusher-drug">
<div class="form-floating col-3">
<input type="text" class="form-control drug-name" placeholder="Name" required value=${drugData.name || ""}>
<label>${ getLocalizedText("menu:item_name") }</label>
</div>
<button type="button" class="btn btn-secondary col-auto choose-item-btn" data-bs-toggle="tooltip" data-bs-placement="top" title="${ getLocalizedText("menu:choose_item") }"><i class="bi bi-list-ul"></i></button>
<div class="form-floating col-auto">
<input type="number" class="form-control drug-min-price" placeholder="Min price" required value=${drugData.minPrice || 0}>
<label>${ getLocalizedText("menu:min_price") }</label>
</div>
<div class="form-floating col-auto">
<input type="number" class="form-control drug-max-price" placeholder="Max price" required value=${drugData.maxPrice || 1000}>
<label>${ getLocalizedText("menu:max_price") }</label>
</div>
<div class="form-floating col-auto">
<input type="number" class="form-control drug-max-quantity" placeholder="Max quantity" required value=${drugData.maxQuantity || 100} data-bs-toggle="tooltip" data-bs-placement="top" title="${ getLocalizedText("menu:pushers:max_quantity_description") }">
<label>${ getLocalizedText("menu:maximum_quantity") }</label>
</div>
<button type="button" class="btn-close btn-close-white remove-result-item-btn ms-4"></button>
</div>
`);
drugDiv.find(".btn-close").click(function() {
drugDiv.remove();
});
// Initialize tooltip so player knows what the field is for
drugDiv.find(".drug-max-quantity").tooltip();
drugDiv.find(".choose-item-btn").tooltip();
drugDiv.find(".choose-item-btn").click(async function() {
const itemName = await itemsDialog();
drugDiv.find(".drug-name").val(itemName);
})
pusherDiv.find(".pusher-drugs-list").append(drugDiv);
}
function addPusher(pusherData = {}) {
let pusherDiv = $(`
<div class="pusher mb-4">
<div class="mb-6 pusher-spawn-location">
<p class="text-center fs-5">${ getLocalizedText("menu:pushers:location") }</p>
<div class="row g-2 row-cols-4 text-body my-2">
<div class="form-floating">
<input type="number" step="0.01" class="form-control max-two-decimals coord-x" placeholder="X" required>
<label>${ getLocalizedText("menu:x") }</label>
</div>
<div class="form-floating">
<input type="number" step="0.01" class="form-control max-two-decimals coord-y" placeholder="Y" required>
<label>${ getLocalizedText("menu:y") }</label>
</div>
<div class="form-floating">
<input type="number" step="0.01" class="form-control max-two-decimals coord-z" placeholder="Z" required>
<label>${ getLocalizedText("menu:z") }</label>
</div>
<div class="form-floating">
<input type="number" step="0.01" class="form-control max-two-decimals heading" placeholder="Heading" required>
<label>${ getLocalizedText("menu:heading") }</label>
</div>
</div>
<button type="button" class="btn btn-secondary mx-1 current-coords-btn">${ getLocalizedText("menu:current_coords_heading") }</button>
</div>
<p class="text-center fs-3 mb-1">${ getLocalizedText("menu:pushers:work_time") }</p>
<p class="text-center fs-5">${ getLocalizedText("menu:pushers:work_time:subtitle") } - <span class="text-warning server-clock"></span></p>
<div class="d-flex justify-content-center gap-3 mb-6">
<div class="form-check form-check-inline my-auto">
<input class="form-check-input is-always-active" type="checkbox" checked>
<label class="form-check-label">${getLocalizedText("menu:is_always_active")}</label>
</div>
<div class="form-floating col">
<input type="number" min="0" max="23" value="0" class="form-control start-hour" placeholder="Start hour" disabled required>
<label>${ getLocalizedText("menu:start_hour") }</label>
<p class="invalid-feedback">${getLocalizedText("menu:invalid_hours")}</p>
</div>
<div class="form-floating col">
<input type="number" min="0" max="23" value="23" class="form-control finish-hour" placeholder="Finish hour" disabled required>
<label>${ getLocalizedText("menu:finish_hour") }</label>
<p class="invalid-feedback">${getLocalizedText("menu:invalid_hours")}</p>
</div>
</div>
<div class="my-4">
<p class="text-center fs-3">${ getLocalizedText("menu:pushers:acceptable_drugs") }</p>
<div class="pusher-drugs-list">
</div>
<div class="d-flex justify-content-between align-items-center mt-3">
<div class="form-check form-switch fs-4 my-auto">
<input class="form-check-input pusher-one-drug-only" type="checkbox" role="switch">
<label class="form-check-label">${ getLocalizedText("menu:pushers:accepts_only_one_random_drug") }</label>
</div>
<button type="button" class="btn btn-secondary add-drug">${ getLocalizedText("menu:pushers:add_drug") }</button>
</div>
</div>
<button type="button" class="btn btn-danger mx-1 remove-btn">${ getLocalizedText("menu:remove") }</button>
</div>
<hr>
`);
pusherDiv.find(".is-always-active").change(function() {
let isAlwaysActive = $(this).prop("checked");
pusherDiv.find(".start-hour").prop("disabled", isAlwaysActive);
pusherDiv.find(".finish-hour").prop("disabled", isAlwaysActive);
if(isAlwaysActive) {
pusherDiv.find(".start-hour").val(0);
pusherDiv.find(".finish-hour").val(23);
}
});
pusherDiv.find(".remove-btn").click(function() {
pusherDiv.remove();
});
pusherDiv.find(".current-coords-btn").click(async function(event) {
if(event.shiftKey) {
const coordsInUI = {
x: parseFloat( pusherDiv.find(".coord-x").val() ),
y: parseFloat( pusherDiv.find(".coord-y").val() ),
z: parseFloat( pusherDiv.find(".coord-z").val() )
}
teleportToCoords(coordsInUI, parseFloat(pusherDiv.find(".heading").val()));
return;
}
const data = await getCurrentCoordsAndHeading();
pusherDiv.find(".coord-x").val(data.coords.x);
pusherDiv.find(".coord-y").val(data.coords.y);
pusherDiv.find(".coord-z").val(data.coords.z);
pusherDiv.find(".heading").val(data.heading);
});
pusherDiv.find(".add-drug").click(function() {
addPusherDrug(pusherDiv);
});
if (pusherData.coords) {
pusherDiv.find(".coord-x").val(pusherData.coords.x);
pusherDiv.find(".coord-y").val(pusherData.coords.y);
pusherDiv.find(".coord-z").val(pusherData.coords.z);
}
if(pusherData.heading) {
pusherDiv.find(".heading").val(pusherData.heading);
}
if (pusherData.workTime) {
pusherDiv.find(".start-hour").val(pusherData.workTime.startHour);
pusherDiv.find(".finish-hour").val(pusherData.workTime.finishHour);
const isAlwaysActive = pusherData.workTime.startHour == 0 && pusherData.workTime.finishHour == 23;
pusherDiv.find(".is-always-active").prop("checked", isAlwaysActive).change();
}
if(pusherData.drugsNeeded) {
pusherData.drugsNeeded.forEach(drug => {
addPusherDrug(pusherDiv, drug);
});
}
$.post(`https://${resName}/getServerClock`, {}, function(time) {
pusherDiv.find(".server-clock").text(time);
});
pusherDiv.find(".pusher-one-drug-only").prop("checked", pusherData.acceptsOnlyOneRandomDrug);
$("#pushers-list").append(pusherDiv);
return pusherDiv;
}
$("#add-pusher-btn").click(function() {
let pusherDiv = addPusher();
addPusherDrug(pusherDiv);
});
function getPusherDrugs(pusherDiv) {
let drugs = [];
pusherDiv.find(".pusher-drug").each(function() {
let drug = {
name: $(this).find(".drug-name").val(),
minPrice: parseInt( $(this).find(".drug-min-price").val() ),
maxPrice: parseInt( $(this).find(".drug-max-price").val() ),
maxQuantity: parseInt( $(this).find(".drug-max-quantity").val() ),
};
drugs.push(drug);
});
return drugs;
}
function getPushers() {
let pushers = {};
let pushersCount = 1;
$("#pushers-list").find(".pusher").each(function() {
pushers[pushersCount] = {
coords: {
x: parseFloat( $(this).find(".coord-x").val() ),
y: parseFloat( $(this).find(".coord-y").val() ),
z: parseFloat( $(this).find(".coord-z").val() )
},
heading: parseFloat( $(this).find(".heading").val() ),
workTime: {
startHour: parseInt( $(this).find(".start-hour").val() ),
finishHour: parseInt( $(this).find(".finish-hour").val() )
},
drugsNeeded: getPusherDrugs($(this)),
acceptsOnlyOneRandomDrug: $(this).find(".pusher-one-drug-only").prop("checked"),
};
pushersCount++;
})
return pushers;
}
$("#enable-explosion-on-error").change(function() {
let enabled = $(this).prop("checked");
$("#seconds-before-explosion").prop("disabled", !enabled);
})
// Load settings
function loadSettings(fullConfig) {
loadModulesSettings(fullConfig.modules);
setSelectiveTargetingSettings(fullConfig.selectiveTargeting);
// Plane selling
$("#enable-plane-selling").prop("checked", fullConfig.enableAirplaneSell);
$("#enable-plane-selling").change();
$("#minimum-plane-height").val(fullConfig.heightToSell);
$("#time-to-sell-in-plane").val(fullConfig.timeToSellInPlane);
$("#enable-plane-police-alert").prop("checked", fullConfig.alarmPoliceInPlane);
$("#plane-use-the-whole-ocean").prop("checked", fullConfig.sellUseWholeOcean.plane);
$("#plane-use-the-whole-ocean").change();
$("#plane-selling-coords-x").val(fullConfig.sellArea.plane.coords.x);
$("#plane-selling-coords-y").val(fullConfig.sellArea.plane.coords.y);
$("#plane-selling-coords-z").val(fullConfig.sellArea.plane.coords.z);
$("#plane-selling-coords-radius").val(fullConfig.sellArea.plane.radius);
fillAcceptableDrugsForDiv("#plane-selling-acceptable-drugs", fullConfig.planeAcceptableDrugs);
// Rewards
setRewardDivData("#plane-selling-reward-div", fullConfig.rewards.plane);
setRewardDivData("#boat-selling-reward-div", fullConfig.rewards.boat);
setRewardDivData("#npc-selling-reward-div", fullConfig.rewards.npc);
setRewardDivData("#narcos-selling-reward-div", fullConfig.rewards.narcos);
setRewardDivData("#pushers-selling-reward-div", fullConfig.rewards.pushers);
$("#plane-minimum-police").val(fullConfig.planeSellingMinimumPolice);
// Boat selling
$("#enable-boat-selling").prop("checked", fullConfig.enableBoatSell);
$("#enable-boat-selling").change();
$("#time-to-sell-in-boat").val(fullConfig.timeToSellInBoat);
$("#enable-boat-police-alert").prop("checked", fullConfig.alarmPoliceInBoat);
$("#boat-use-the-whole-ocean").prop("checked", fullConfig.sellUseWholeOcean.boat);
$("#boat-use-the-whole-ocean").change();
$("#boat-selling-coords-x").val(fullConfig.sellArea.boat.coords.x);
$("#boat-selling-coords-y").val(fullConfig.sellArea.boat.coords.y);
$("#boat-selling-coords-z").val(fullConfig.sellArea.boat.coords.z);
$("#boat-selling-coords-radius").val(fullConfig.sellArea.boat.radius);
fillAcceptableDrugsForDiv("#boat-selling-acceptable-drugs", fullConfig.boatAcceptableDrugs);
$("#boat-minimum-police").val(fullConfig.boatSellingMinimumPolice);
// NPC Selling
$("#enable-npc-selling").prop("checked", fullConfig.enableNPCSell);
$("#enable-npc-selling").change();
$("#npc-selling-min-quantity").val(fullConfig.minNPCSellQuantity);
$("#npc-selling-max-quantity").val(fullConfig.maxNPCSellQuantity);
$("#npc-accept-chances").val(fullConfig.sellToNPCChancesToAccept);
$("#npc-max-drug-quantity").val(fullConfig.maxNPCsSellableDrugQuantity);
fillAcceptableDrugsForDiv("#npc-selling-acceptable-drugs", fullConfig.npcAcceptableDrugs);
$("#npc-minimum-police").val(fullConfig.npcSellingMinimumPolice);
$("#npc-seconds-to-sell").val(fullConfig.npcSecondsToSell);
$("#npc-alert-police-chances").val(fullConfig.npcAlertPoliceChances);
$("#npc-can-rob-player").prop("checked", fullConfig.canNPCRobPlayer);
$("#npc-can-attack-player").prop("checked", fullConfig.canNPCAttackPlayer);
// NPC Selling command
$("#npc-selling-command-is-enabled").prop("checked", fullConfig.npcSellingCommand.enabled).change();
$("#npc-selling-command").val(fullConfig.npcSellingCommand.command);
$("#npc-selling-command-has-to-spawn-npc").prop("checked", fullConfig.npcSellingCommand.hasToSpawnPed);
// Narcos selling
$("#enable-narcos-selling").prop("checked", fullConfig.enableNarcosSelling);
$("#enable-narcos-selling").change();
$("#narcos-model").val(fullConfig.narcosModel),
$("#enable-narcos-blip").prop("checked", fullConfig.showNarcosBlip),
$("#enable-narcos-blip").change();
$("#narcos-selling-blip-name").val(fullConfig.narcosBlip.name);
$("#narcos-selling-blip-color").val(fullConfig.narcosBlip.color);
$("#narcos-selling-blip-scale").val(fullConfig.narcosBlip.scale);
$("#narcos-selling-blip-sprite").val(fullConfig.narcosBlip.sprite);
$("#narcos-location-change").val(fullConfig.narcosLocationChangeTime);
$("#narcos-police-alert-chances").val(fullConfig.narcosCallPoliceChances);
$("#narcos-accepts-only-one-buyer-per-location").prop("checked", fullConfig.narcosAcceptsOnlyOneBuyerPerLocation);
$("#narcos-spawn-locations").empty();
if(fullConfig.narcosBuyerLocations) {
fullConfig.narcosBuyerLocations.forEach(location => {
addNarcosSpawnLocation(location.coords, location.heading);
});
}
$("#narcos-selling-acceptable-drugs").empty();
if(fullConfig.narcosNeededDrugs) {
fullConfig.narcosNeededDrugs.forEach(drug => {
addNarcosDrug(drug.name, drug.minQuantity, drug.maxQuantity, drug.minPrice, drug.maxPrice);
});
}
$("#narcos-minimum-police").val(fullConfig.narcosSellingMinimumPolice);
// Pushers
$("#enable-pushers-selling").prop("checked", fullConfig.arePushersEnabled);
$("#enable-pushers-selling").change();
$("#pushers-model").val(fullConfig.pusherModel),
$("#enable-pushers-blip").prop("checked", fullConfig.showPushersBlips);
$("#pushers-selling-blip-name").val(fullConfig.pusherBlip.name);
$("#pushers-selling-blip-color").val(fullConfig.pusherBlip.color);
$("#pushers-selling-blip-scale").val(fullConfig.pusherBlip.scale);
$("#pushers-selling-blip-sprite").val(fullConfig.pusherBlip.sprite);
$("#pushers-alert-police-chances").val(fullConfig.pushersCallPoliceChances);
$("#pushers-list").empty();
if(fullConfig.pushers) {
for(const[pusherId, pusherData] of Object.entries(fullConfig.pushers)) {
addPusher(pusherData);
}
}
$("#pushers-minimum-police").val(fullConfig.pushersSellingMinimumPolice);
// Pocket craftings
$("#pocket-craftings-list").empty();
if(fullConfig.pocketCraftings) {
for(const[itemName, itemData] of Object.entries(fullConfig.pocketCraftings)) {
addPocketCrafting(itemName, itemData);
}
}
// Effects
$("#drugs-effects").empty();
if(fullConfig.drugsEffects) {
for(const[itemName, effectData] of Object.entries(fullConfig.drugsEffects)) {
addItemEffect(itemName, effectData);
}
}
// Settings
setTomSelectValue("#settings_locale", fullConfig.locale)
setTomSelectValue("#settings_menuPosition", fullConfig.menuPosition)
setTomSelectValue("#settings-targeting-script", fullConfig.targetingScript)
$("#enable-fire-on-error").prop("checked", fullConfig.enableFireOnError);
$("#enable-explosion-on-error").prop("checked", fullConfig.enableExplosionOnError).change();
$("#seconds-before-explosion").val(fullConfig.secondsBeforeExplosion);
$("#enable-discord-logs").prop("checked", fullConfig.areDiscordLogsActive).change()
$("#main-discord-webhook").val(fullConfig.mainDiscordWebhook);
if(fullConfig.specificWebhooks) {
for(const[webhookType, webhookUrl] of Object.entries(fullConfig.specificWebhooks)) {
$("#discord-specific-webhooks").find(`[data-webhook-type="${webhookType}"]`).val(webhookUrl);
}
}
// Key to sell to NPCs
$("#key-to-sell-to-npcs").val(fullConfig.keyToSellToNPCs);
// Minimum police
$("#harvestable-items-minimum-police").val(fullConfig.harvestingItemsMinimumPolice);
$("#laboratory-minimum-police").val(fullConfig.useLaboratoryMinimumPolice);
$("#fields-minimum-police").val(fullConfig.harvestFieldMinimumPolice);
// Automatic farm
$("#allow-afk-farming-for-harvest").prop("checked", fullConfig.allowAfkFarmingForHarvest);
$("#allow-afk-farming-for-laboratories").prop("checked", fullConfig.allowAfkFarmingForLaboratories);
// Price reduction
$("#minimum-cops-for-base-price").val(fullConfig.priceReduction.minimumPolice);
$("#price-reduction-percentage").val(fullConfig.priceReduction.reductionPercentage);
$("#price-reduction-interval").val(fullConfig.priceReduction.reductionInterval);
// Auctions
$("#auction-check-interval").val(fullConfig.auctions.nextAuctionEachMinutes);
}
// Settings
function getSpecificWebhooks() {
let webhooks = {};
$("#discord-specific-webhooks").find(".webhook").each(function() {
let webhookType = $(this).data("webhookType");
let webhook = $(this).val();
if(webhook) {
webhooks[webhookType] = webhook;
}
})
return webhooks;
}
function toggleSelectiveTargeting() {
const enabled = $("#settings-targeting-script").val() != "none";
$("#selective-targeting-container").find(".form-check-input").prop("disabled", !enabled);
}
function getSelectiveTargetingSettings() {
let selectiveTargeting = {};
$("#selective-targeting-container").find(".form-check-input").each(function(index, element) {
element = $(element);
let featureName = element.data("featureName");
let enabled = element.prop("checked");
selectiveTargeting[featureName] = enabled;
});
return selectiveTargeting;
}
function setSelectiveTargetingSettings(selectiveTargeting) {
$("#selective-targeting-container").find(".form-check-input").each(function(index, element) {
element = $(element);
let featureName = element.data("featureName");
let enabled = selectiveTargeting[featureName];
element.prop("checked", enabled);
});
toggleSelectiveTargeting();
}
$("#settings-targeting-script").change(function() {
toggleSelectiveTargeting();
})
$("#settings").submit(async function(event) {
if(isThereAnyErrorInForm(event)) return;
let clientSettings = {
secondsBeforeExplosion: parseInt( $("#seconds-before-explosion").val() ),
menuPosition: $("#settings_menuPosition").val(),
// Key to sell to NPCs
keyToSellToNPCs: parseInt( $("#key-to-sell-to-npcs").val() ),
// Targeting
targetingScript: $("#settings-targeting-script").val(),
selectiveTargeting: getSelectiveTargetingSettings()
}
let sharedSettings = {
locale: $("#settings_locale").val(),
// NPC selling command
npcSellingCommand: {
enabled: $("#npc-selling-command-is-enabled").prop("checked"),
command: $("#npc-selling-command").val(),
hasToSpawnPed: $("#npc-selling-command-has-to-spawn-npc").prop("checked")
},
modules: getModulesSettings(),
}
let serverSettings = {
enableFireOnError: $("#enable-fire-on-error").prop("checked"),
enableExplosionOnError: $("#enable-explosion-on-error").prop("checked"),
areDiscordLogsActive: $("#enable-discord-logs").prop("checked"),
mainDiscordWebhook: $("#main-discord-webhook").val(),
specificWebhooks: getSpecificWebhooks(),
harvestingItemsMinimumPolice: parseInt( $("#harvestable-items-minimum-police").val() ),
useLaboratoryMinimumPolice: parseInt( $("#laboratory-minimum-police").val() ),
harvestFieldMinimumPolice: parseInt( $("#fields-minimum-police").val() ),
allowAfkFarmingForHarvest: $("#allow-afk-farming-for-harvest").prop("checked"),
allowAfkFarmingForLaboratories: $("#allow-afk-farming-for-laboratories").prop("checked"),
priceReduction: {
minimumPolice: parseInt( $("#minimum-cops-for-base-price").val()),
reductionPercentage: parseInt( $("#price-reduction-percentage").val()),
reductionInterval: parseInt( $("#price-reduction-interval").val())
},
auctions: {
nextAuctionEachMinutes: parseInt( $("#auction-check-interval").val() ),
}
}
const response = await $.post(`https://${resName}/saveSettings`, JSON.stringify({
clientSettings: clientSettings,
serverSettings: serverSettings,
sharedSettings: sharedSettings
}));
showServerResponse(response);
refreshTranslations(sharedSettings.locale);
})
$("#enable-discord-logs").change(function() {
let enabled = $(this).prop("checked");
$("#main-discord-webhook").prop("disabled", !enabled);
$("#main-discord-webhook").prop("required", enabled);
$("#discord-specific-webhooks").find(".webhook").each(function() {
$(this).prop("disabled", !enabled);
});
})
// Restore to default config
$(".restore-to-default-config").click(async function() {
if(!await confirmDeletion( getLocalizedText("menu:are_you_sure_to_restore_settings") )) return;
const defaultConfig = await $.post(`https://${resName}/getDefaultConfiguration`);
loadSettings(defaultConfig)
})
// Drugs effects
async function getItemLabel(itemName) {
return new Promise(resolve => {
$.post(`https://${resName}/getItemLabel`, JSON.stringify({itemName: itemName}), function(itemLabel) {
resolve(itemLabel);
});
})
}
async function addItemEffect(itemName, itemData={}, isNew = false) {
let itemLabel = await getItemLabel(itemName);
let itemEffectDiv = $(`
<div class="drug-effect mb-4">
<p class="text-center fs-2 fw-bold title">${itemLabel} <span class="fs-5 fw-lighter fst-italic">(${itemName})</span></p>
<div class="effects mt-3">
<p class="text-start fs-3 fw-bold mb-2">${ getLocalizedText("menu:effects:taking_method") }</p>
<div class="d-flex mb-4">
<div class="form-check form-check-inline fs-4">
<input class="form-check-input" type="radio" name="drug-assume-type-${itemName}" value="pill" required checked>
<label class="form-check-label">
${ getLocalizedText("menu:effects:pill") }
</label>
</div>
<div class="form-check form-check-inline fs-4">
<input class="form-check-input" type="radio" name="drug-assume-type-${itemName}" value="drink">
<label class="form-check-label">
${ getLocalizedText("menu:effects:drink") }
</label>
</div>
<div class="form-check form-check-inline fs-4">
<input class="form-check-input" type="radio" name="drug-assume-type-${itemName}" value="drink_soda">
<label class="form-check-label">
${ getLocalizedText("menu:effects:drink_soda") }
</label>
</div>
<div class="form-check form-check-inline fs-4">
<input class="form-check-input" type="radio" name="drug-assume-type-${itemName}" value="smoke">
<label class="form-check-label">
${ getLocalizedText("menu:effects:smoke") }
</label>
</div>
<div class="form-check form-check-inline fs-4">
<input class="form-check-input" type="radio" name="drug-assume-type-${itemName}" value="snort">
<label class="form-check-label">
${ getLocalizedText("menu:effects:snort") }
</label>
</div>
<div class="form-check form-check-inline fs-4">
<input class="form-check-input" type="radio" name="drug-assume-type-${itemName}" value="needle">
<label class="form-check-label">
${ getLocalizedText("menu:effects:needle") }
</label>
</div>
</div>
<p class="text-start fs-3 fw-bold mb-2">${ getLocalizedText("menu:effects:drunk_effects") }</p>
<div class="d-flex mb-4">
<div class="form-check form-check-inline fs-4">
<input class="form-check-input stackable-effect" type="checkbox" value="visual_shaking">
<label class="form-check-label">${ getLocalizedText("menu:effects:visual_shaking") }</label>
</div>
<div class="form-check form-check-inline fs-4">
<input class="form-check-input stackable-effect" type="checkbox" value="drunk_walk">
<label class="form-check-label">${ getLocalizedText("menu:effects:drunk_walk") }</label>
</div>
<div class="form-check form-check-inline fs-4">
<input class="form-check-input stackable-effect" type="checkbox" value="fall">
<label class="form-check-label">${ getLocalizedText("menu:effects:fall") }</label>
</div>
</div>
<p class="text-start fs-3 fw-bold mb-2">${ getLocalizedText("menu:effects:visual_color_effects") }</p>
<div class="d-flex row-cols-3 flex-wrap mb-4 mb-4">
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input" type="radio" name="camera-color-effects-${itemName}" value="none" required checked>
<label class="form-check-label">
${ getLocalizedText("menu:effects:none") }
</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input" type="radio" name="camera-color-effects-${itemName}" value="pink_visual">
<label class="form-check-label">
${ getLocalizedText("menu:effects:pink_visual") }
</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input" type="radio" name="camera-color-effects-${itemName}" value="green_visual">
<label class="form-check-label">
${ getLocalizedText("menu:effects:green_visual") }
</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input" type="radio" name="camera-color-effects-${itemName}" value="confused_visual">
<label class="form-check-label">
${ getLocalizedText("menu:effects:confused_visual") }
</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input" type="radio" name="camera-color-effects-${itemName}" value="yellow_visual">
<label class="form-check-label">
${ getLocalizedText("menu:effects:yellow_visual") }
</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input" type="radio" name="camera-color-effects-${itemName}" value="blurred_visual">
<label class="form-check-label">
${ getLocalizedText("menu:effects:blurred_visual") }
</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input" type="radio" name="camera-color-effects-${itemName}" value="red_visual">
<label class="form-check-label">
${ getLocalizedText("menu:effects:red_visual") }
</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input" type="radio" name="camera-color-effects-${itemName}" value="foggy_visual">
<label class="form-check-label">
${ getLocalizedText("menu:effects:foggy_visual") }
</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input" type="radio" name="camera-color-effects-${itemName}" value="blue_visual">
<label class="form-check-label">
${ getLocalizedText("menu:effects:blue_visual") }
</label>
</div>
</div>
<p class="text-start fs-3 fw-bold mb-2">${ getLocalizedText("menu:effects:perks") }</p>
<div class="d-flex row-cols-3 flex-wrap mb-4 mb-4">
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input stackable-effect" type="checkbox" value="armor50">
<label class="form-check-label">${ getLocalizedText("menu:effects:50_percent_armor") }</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input stackable-effect" type="checkbox" value="armor100">
<label class="form-check-label">${ getLocalizedText("menu:effects:100_percent_armor") }</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input stackable-effect" type="checkbox" value="health50">
<label class="form-check-label">${ getLocalizedText("menu:effects:50_percent_health") }</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input stackable-effect" type="checkbox" value="health100">
<label class="form-check-label">${ getLocalizedText("menu:effects:100_percent_health") }</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input stackable-effect" type="checkbox" value="sprint_faster">
<label class="form-check-label">${ getLocalizedText("menu:effects:faster_sprint") }</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input stackable-effect" type="checkbox" value="swim_faster">
<label class="form-check-label">${ getLocalizedText("menu:effects:faster_swim") }</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input stackable-effect" type="checkbox" value="infinite_stamina">
<label class="form-check-label">${ getLocalizedText("menu:effects:infinite_stamina") }</label>
</div>
<div class="form-check form-check-inline fs-4 m-0">
<input class="form-check-input stackable-effect" type="checkbox" value="remove_old_effects">
<label class="form-check-label">${ getLocalizedText("menu:effects:remove_old_effects") }</label>
</div>
</div>
<p class="text-start fs-3 fw-bold mb-0">${ getLocalizedText("menu:effects:cumulative_effect") }</p>
<div class="d-flex row-cols-3 flex-wrap">
<div class="d-flex align-items-center gap-3 my-1 cumulative-effect" data-cumulative-name="armor">
<div class="form-check form-check-inline fs-4 me-3 my-auto">
<input class="form-check-input cumulative-effect-checkbox" type="checkbox" value="">
<label class="form-check-label">${ getLocalizedText("menu:effects:armor") }</label>
</div>
<div class="form-check my-auto">
<input class="form-check-input" type="radio" name="drug-cumulative-armor-${itemName}" value="increase">
<label class="form-check-label">${getLocalizedText("menu:effects:increase")}</label>
</div>
<div class="form-check my-auto">
<input class="form-check-input" type="radio" name="drug-cumulative-armor-${itemName}" value="decrease">
<label class="form-check-label">${getLocalizedText("menu:effects:decrease")}</label>
</div>
<div class="form-floating col-3">
<input type="number" min=1 class="form-control cumulative-effect-amount" placeholder="..." value="20">
<label>${ getLocalizedText("menu:amount") }</label>
</div>
</div>
<div class="d-flex align-items-center gap-3 my-1 cumulative-effect" data-cumulative-name="health">
<div class="form-check form-check-inline fs-4 me-3 my-auto">
<input class="form-check-input cumulative-effect-checkbox" type="checkbox" value="">
<label class="form-check-label">${ getLocalizedText("menu:effects:health") }</label>
</div>
<div class="form-check my-auto">
<input class="form-check-input" type="radio" name="drug-cumulative-health-${itemName}" value="increase">
<label class="form-check-label">${getLocalizedText("menu:effects:increase")}</label>
</div>
<div class="form-check my-auto">
<input class="form-check-input" type="radio" name="drug-cumulative-health-${itemName}" value="decrease">
<label class="form-check-label">${getLocalizedText("menu:effects:decrease")}</label>
</div>
<div class="form-floating col-3">
<input type="number" min=1 class="form-control cumulative-effect-amount" placeholder="..." value="20">
<label>${ getLocalizedText("menu:amount") }</label>
</div>
</div>
<div class="d-flex align-items-center gap-3 my-1 cumulative-effect" data-cumulative-name="stress">
<div class="form-check form-check-inline fs-4 me-3 my-auto">
<input class="form-check-input cumulative-effect-checkbox" type="checkbox" value="">
<label class="form-check-label">${ getLocalizedText("menu:effects:stress") }</label>
</div>
<div class="form-check my-auto">
<input class="form-check-input" type="radio" name="drug-cumulative-stress-${itemName}" value="increase">
<label class="form-check-label">${getLocalizedText("menu:effects:increase")}</label>
</div>
<div class="form-check my-auto">
<input class="form-check-input" type="radio" name="drug-cumulative-stress-${itemName}" value="decrease">
<label class="form-check-label">${getLocalizedText("menu:effects:decrease")}</label>
</div>
<div class="form-floating col-3">
<input type="number" min=1 class="form-control cumulative-effect-amount" placeholder="..." value="20">
<label>${ getLocalizedText("menu:amount") }</label>
</div>
</div>
<div class="d-flex align-items-center gap-3 my-1 cumulative-effect" data-cumulative-name="hunger">
<div class="form-check form-check-inline fs-4 me-3 my-auto">
<input class="form-check-input cumulative-effect-checkbox" type="checkbox" value="">
<label class="form-check-label">${ getLocalizedText("menu:effects:hunger") }</label>
</div>
<div class="form-check my-auto">
<input class="form-check-input" type="radio" name="drug-cumulative-hunger-${itemName}" value="increase">
<label class="form-check-label">${getLocalizedText("menu:effects:increase")}</label>
</div>
<div class="form-check my-auto">
<input class="form-check-input" type="radio" name="drug-cumulative-hunger-${itemName}" value="decrease">
<label class="form-check-label">${getLocalizedText("menu:effects:decrease")}</label>
</div>
<div class="form-floating col-3">
<input type="number" min=1 class="form-control cumulative-effect-amount" placeholder="..." value="20">
<label>${ getLocalizedText("menu:amount") }</label>
</div>
</div>
<div class="d-flex align-items-center gap-3 my-1 cumulative-effect" data-cumulative-name="thirst">
<div class="form-check form-check-inline fs-4 me-3 my-auto">
<input class="form-check-input cumulative-effect-checkbox" type="checkbox" value="">
<label class="form-check-label">${ getLocalizedText("menu:effects:thirst") }</label>
</div>
<div class="form-check my-auto">
<input class="form-check-input" type="radio" name="drug-cumulative-thirst-${itemName}" value="increase">
<label class="form-check-label">${getLocalizedText("menu:effects:increase")}</label>
</div>
<div class="form-check my-auto">
<input class="form-check-input" type="radio" name="drug-cumulative-thirst-${itemName}" value="decrease">
<label class="form-check-label">${getLocalizedText("menu:effects:decrease")}</label>
</div>
<div class="form-floating col-3">
<input type="number" min=1 class="form-control cumulative-effect-amount" placeholder="..." value="20">
<label>${ getLocalizedText("menu:amount") }</label>
</div>
</div>
</div>
<p class="text-start fs-3 fw-bold mb-2">${ getLocalizedText("menu:effects:special_effect") }</p>
<div class="d-flex mb-4">
<div class="form-check form-check-inline fs-4">
<input class="form-check-input" type="radio" name="drug-special-effect-${itemName}" value="none" checked>
<label class="form-check-label">${ getLocalizedText("menu:effects:none") }</label>
</div>
<div class="form-check form-check-inline fs-4">
<input class="form-check-input" type="radio" name="drug-special-effect-${itemName}" value="vehicle_stalker">
<label class="form-check-label">${ getLocalizedText("menu:effects:vehicle_stalker") }</label>
</div>
<div class="form-check form-check-inline fs-4">
<input class="form-check-input" type="radio" name="drug-special-effect-${itemName}" value="ghost">
<label class="form-check-label">${ getLocalizedText("menu:effects:ghost") }</label>
</div>
</div>
<div class="form-floating mb-3 text-body">
<input type="number" min=1 class="form-control effect-duration" placeholder="Effects duration" required value="120">
<label>${ getLocalizedText("menu:effects:duration") }</label>
</div>
</div>
<div class="d-inline-block col-12">
<button type="button" class="btn btn-danger float-end remove-effect-btn">${ getLocalizedText("menu:effects:remove_this_effect") }</button>
</div>
</div>
<hr>
`);
if(itemData.takingMethod) {
itemEffectDiv.find(`input[name=drug-assume-type-${itemName}][value=${itemData.takingMethod}]`).prop("checked", true);
}
if(itemData.effectsDuration) {
itemEffectDiv.find(".effect-duration").val(itemData.effectsDuration);
}
if(itemData.effects) {
for(let effect of itemData.effects) {
itemEffectDiv.find(`input[value=${effect}]`).prop("checked", true);
}
}
if(itemData.cumulativeEffects) {
for(let cumulativeEffect of itemData.cumulativeEffects) {
itemEffectDiv.find(`.cumulative-effect[data-cumulative-name=${cumulativeEffect.type}]`).find(".cumulative-effect-checkbox").prop("checked", true);
itemEffectDiv.find(`input[name=drug-cumulative-${cumulativeEffect.type}-${itemName}][value=${cumulativeEffect.action}]`).prop("checked", true);
itemEffectDiv.find(`.cumulative-effect[data-cumulative-name=${cumulativeEffect.type}]`).find(".cumulative-effect-amount").val(cumulativeEffect.amount);
}
}
if(isNew) {
itemEffectDiv.find(".title").append(`<span class="fs-5 fw-lighter fst-italic text-danger"> - ${ getLocalizedText("menu:effects:may_need_restart") }</span>`)
}
itemEffectDiv.data("itemName", itemName);
itemEffectDiv.find(".remove-effect-btn").click(function() {
itemEffectDiv.remove();
})
$("#drugs-effects").append(itemEffectDiv);
}
function getEffectsDataFromDiv(effectDiv, itemName) {
let effectData = {
takingMethod: $(`input[name=drug-assume-type-${itemName}]:checked`).val(),
effects: [],
effectsDuration: parseInt( effectDiv.find(".effect-duration").val() ),
cumulativeEffects: []
};
let visualColorEffect = $(`input[name=camera-color-effects-${itemName}]:checked`).val();
if(visualColorEffect != "none") {
effectData.effects.push(visualColorEffect)
}
const specialEffect = $(`input[name=drug-special-effect-${itemName}]:checked`).val();
if(specialEffect != "none") {
effectData.effects.push(specialEffect)
}
effectDiv.find(".cumulative-effect").each(function() {
if (!$(this).find(".cumulative-effect-checkbox").prop("checked")) return;
const type = $(this).data("cumulativeName");
const action = $(this).find("input[type=radio]:checked").val() || "increase"; // In case people don't select any
const amount = parseInt( $(this).find(".cumulative-effect-amount").val() );
effectData.cumulativeEffects.push({type, action, amount});
});
effectDiv.find(".stackable-effect:checked").each(function() {
effectData.effects.push( $(this).val() );
});
return effectData;
}
function getAllEffectsData() {
let effectsData = {};
$("#drugs-effects").find(".drug-effect").each(function() {
let itemName = $(this).data("itemName");
effectsData[itemName] = getEffectsDataFromDiv($(this), itemName);
});
return effectsData;
}
$("#create-new-item-effect-btn").click(async function() {
const itemName = await itemsDialog();
addItemEffect(itemName, {}, true);
});
// Saves the new effects
$("#effects").submit(async function(event) {
if(isThereAnyErrorInForm(event)) return;
let serverSettings = {
drugsEffects: getAllEffectsData(),
}
let clientSettings = {}
let sharedSettings = {}
const response = await $.post(`https://${resName}/saveSettings`, JSON.stringify({
clientSettings: clientSettings,
serverSettings: serverSettings,
sharedSettings: sharedSettings
}));
showServerResponse(response);
});
/* AUCTIONS */
let auctionsDatatable = $("#auctions-container").DataTable( {
"lengthMenu": [10, 15, 20],
"columnDefs": [
{
"targets": -1, // Ultima colonna
"data": null,
"className": "col-1",
"defaultContent": `
<div class="d-inline-flex gap-3">
<button class='btn btn-success py-0 px-2 force-start-auction-btn' data-bs-toggle="tooltip" data-bs-placement="top" title="Start now"><i class=\"bi bi-play-fill\"></i></button>
</div>
`
}
],
"createdRow": function ( row, data, index ) {
$(row).addClass("clickable");
$(row).click(function() {
let auctionId = parseInt(data[0]);
editAuction(auctionId);
});
$(row).find(".force-start-auction-btn").click(async function(event) {
event.stopPropagation();
const auctionId = parseInt(data[0]);
$.post(`https://${resName}/forceStartAuction`, JSON.stringify({auctionId}));
}).tooltip().prop("title", getLocalizedText("menu:start_now"));
},
} );
let auctions = {};
async function loadAuctions() {
const rawAuctions = await $.post(`https://${resName}/getAllAuctions`);
// Manually create the table to avoid incompatibilities due table indexing
auctions = {};
for(const[k, auction] of Object.entries(rawAuctions)) {
auctions[auction.id] = auction;
}
auctionsDatatable.clear();
for(const[auctionId, auction] of Object.entries(auctions)) {
auctionsDatatable.row.add([
auctionId || "???",
auction.label || "???",
])
}
auctionsDatatable.draw()
}
function setAuctionConfig(auction) {
const config = auction ? auction.config : {};
$("#auction-label").val(auction?.label || "Default");
$("#auction-min-players-online").val(config.minPlayersForAuction ?? 3);
$("#auction-min-police-online").val(config.minPoliceOnline ?? 0);
$("#auction-randomize-items").prop("checked", config.randomizeItems ?? false);
$("#auction-multiplier").val(config.multiplier ?? 1);
$("#auction-max-rounds").val(config.maxRounds ?? 3);
$("#auction-alert-police-after-round").val(config.alertPoliceAfterRound ?? 2);
$("#auction-time-between-rounds").val(config.timeBetweenRoundsSeconds ?? 30);
$("#auction-warning-before-start").val(config.warningBeforeAuctionStartMinutes ?? 15);
$("#auction-no-bid-timeout").val(config.noBidTimeoutSeconds ?? 30);
$("#auction-min-bid-increment").val(config.minBidIncrement ?? 100);
$("#auction-blip-btn").data("blipData", config.blipData ?? getDefaultBlipCustomization());
if(config.accountToPayWith) {
setRewardDivData("#auction-account-to-pay-with", config.accountToPayWith);
}
// Locations
$("#auction-locations-list").empty();
if(config.locations) {
config.locations.forEach(location => {
addAuctionLocation(location);
});
}
// Items
$("#auction-items-list").empty();
if(config.objectsRewards) {
config.objectsRewards.forEach(item => {
addAuctionItem(item);
});
}
}
$("#new-auction-btn").click(function() {
let auctionModal = $("#auction-modal");
auctionModal.modal("show");
auctionModal.data("action", "create");
setAuctionConfig();
// Adapts the modal from edit mode to create mode
$("#delete-auction-btn").hide();
$("#auction-modal-confirm-btn").text( getLocalizedText("menu:create") );
})
function editAuction(auctionId) {
let auction = auctions[auctionId];
let auctionModal = $("#auction-modal");
auctionModal.modal("show");
auctionModal.data("action", "edit");
auctionModal.data("auctionId", auctionId);
setAuctionConfig(auction);
// Adapts the modal from create mode to edit mode
$("#delete-auction-btn").show();
$("#auction-modal-confirm-btn").text( getLocalizedText("menu:save") );
}
function getAuctionSetup() {
return {
label: $("#auction-label").val(),
config: {
minPlayersForAuction: parseInt( $("#auction-min-players-online").val() ),
minPoliceOnline: parseInt( $("#auction-min-police-online").val() ),
blipData: $("#auction-blip-btn").data("blipData"),
randomizeItems: $("#auction-randomize-items").prop("checked"),
multiplier: parseFloat( $("#auction-multiplier").val() ),
maxRounds: parseInt( $("#auction-max-rounds").val() ),
alertPoliceAfterRound: parseInt( $("#auction-alert-police-after-round").val() ),
accountToPayWith: getRewardDivData("#auction-account-to-pay-with"),
timeBetweenRoundsSeconds: parseInt( $("#auction-time-between-rounds").val() ),
warningBeforeAuctionStartMinutes: parseInt( $("#auction-warning-before-start").val() ),
noBidTimeoutSeconds: parseInt( $("#auction-no-bid-timeout").val() ),
minBidIncrement: parseInt( $("#auction-min-bid-increment").val() ),
objectsRewards: getAuctionItems(),
locations: getAuctionLocations()
}
}
}
$("#auction-form").submit(async function(event) {
if(isThereAnyErrorInForm(event)) return;
let auction = getAuctionSetup();
let modal = $("#auction-modal");
let action = $(modal).data("action");
let response = null;
switch(action) {
case "create": {
response = await $.post(`https://${resName}/createNewAuction`, JSON.stringify(auction));
break;
}
case "edit": {
let auctionId = modal.data("auctionId");
response = await $.post(`https://${resName}/updateAuction`, JSON.stringify({
auctionId,
auction
}));
break;
}
}
modal.modal("hide");
loadAuctions();
showServerResponse(response);
});
$("#delete-auction-btn").click(async function() {
if(!await confirmDeletion()) return;
let auctionId = $("#auction-modal").data("auctionId");
const response = await $.post(`https://${resName}/deleteAuction`, JSON.stringify({auctionId}));
$("#auction-modal").modal("hide");
loadAuctions();
showServerResponse(response);
})
function addAuctionLocation(location = {}) {
const div = $(`
<div class="auction-location">
<h5 class="text-center mb-1 mt-4">${getLocalizedText("menu:broker_ped")}</h5>
<div class="d-flex justify-content-center align-items-center gap-3">
<div class="form-floating">
<input type="text" class="form-control broker-model" placeholder="...">
<label>${getLocalizedText("menu:model")}</label>
</div>
<a class="btn btn-secondary px-4" href="https://docs.fivem.net/docs/game-references/ped-models/" target="_blank" onclick='window.invokeNative("openUrl", "https://docs.fivem.net/docs/game-references/ped-models/")'><i class="bi bi-box-arrow-up-right"></i></a>
<div class="form-floating col-1">
<input type="text" class="form-control broker-coordinates-x" placeholder="...">
<label>${getLocalizedText("menu:x")}</label>
</div>
<div class="form-floating col-1">
<input type="text" class="form-control broker-coordinates-y" placeholder="...">
<label>${getLocalizedText("menu:y")}</label>
</div>
<div class="form-floating col-1">
<input type="text" class="form-control broker-coordinates-z" placeholder="...">
<label>${getLocalizedText("menu:z")}</label>
</div>
<div class="form-floating col-auto">
<input type="text" class="form-control broker-heading" placeholder="...">
<label>${getLocalizedText("menu:heading")}</label>
</div>
<button type="button" class="btn btn-secondary px-4 choose-broker-coordinates-btn"><i class="bi bi-geo"></i></button>
</div>
<h5 class="text-center mb-1 mt-4">${getLocalizedText("menu:broker_vehicle")}</h5>
<div class="d-flex justify-content-center align-items-center gap-3">
<div class="form-floating">
<input type="text" class="form-control vehicle-model" placeholder="...">
<label>${getLocalizedText("menu:model")}</label>
</div>
<a class="btn btn-secondary px-4" href="https://docs.fivem.net/docs/game-references/vehicle-models/" target="_blank" onclick='window.invokeNative("openUrl", "https://docs.fivem.net/docs/game-references/vehicle-models/")'><i class="bi bi-box-arrow-up-right"></i></a>
<div class="form-floating col-1">
<input type="text" class="form-control vehicle-coordinates-x" placeholder="...">
<label>${getLocalizedText("menu:x")}</label>
</div>
<div class="form-floating col-1">
<input type="text" class="form-control vehicle-coordinates-y" placeholder="...">
<label>${getLocalizedText("menu:y")}</label>
</div>
<div class="form-floating col-1">
<input type="text" class="form-control vehicle-coordinates-z" placeholder="...">
<label>${getLocalizedText("menu:z")}</label>
</div>
<div class="form-floating col-auto">
<input type="text" class="form-control vehicle-heading" placeholder="...">
<label>${getLocalizedText("menu:heading")}</label>
</div>
<button type="button" class="btn btn-secondary px-4 choose-vehicle-coordinates-btn"><i class="bi bi-geo"></i></button>
</div>
<h5 class="text-center mb-1 mt-4">${getLocalizedText("menu:text")}</h5>
<div class="d-flex justify-content-center align-items-center gap-3">
<div class="form-floating col-1">
<input type="text" class="form-control text-coordinates-x" placeholder="...">
<label>${getLocalizedText("menu:x")}</label>
</div>
<div class="form-floating col-1">
<input type="text" class="form-control text-coordinates-y" placeholder="...">
<label>${getLocalizedText("menu:y")}</label>
</div>
<div class="form-floating col-1">
<input type="text" class="form-control text-coordinates-z" placeholder="...">
<label>${getLocalizedText("menu:z")}</label>
</div>
<button type="button" class="btn btn-secondary px-4 choose-text-coordinates-btn"><i class="bi bi-geo"></i></button>
</div>
<button type="button" class="btn btn-danger px-4 remove-location-btn">${getLocalizedText("menu:remove_location")}</button>
<hr class="thick-hr">
</div>
`);
div.find(".remove-location-btn").click(function() {
div.remove();
});
// Broker
div.find(".broker-model").val(location.broker?.model || "s_m_m_highsec_01");
div.find(".broker-coordinates-x").val(location.broker?.coordinates?.x || "");
div.find(".broker-coordinates-y").val(location.broker?.coordinates?.y || "");
div.find(".broker-coordinates-z").val(location.broker?.coordinates?.z || "");
div.find(".broker-heading").val(location.broker?.heading || "");
div.find(".choose-broker-coordinates-btn").click(async function() {
const model = div.find(".broker-model").val();
const data = await placeEntity(model, "ped");
if(!data) return;
div.find(".broker-coordinates-x").val(data.coords.x);
div.find(".broker-coordinates-y").val(data.coords.y);
div.find(".broker-coordinates-z").val(data.coords.z);
div.find(".broker-heading").val(data.heading);
});
// Broker vehicle
div.find(".vehicle-model").val(location.vehicle?.model || "burrito3");
div.find(".vehicle-coordinates-x").val(location.vehicle?.coordinates?.x || "");
div.find(".vehicle-coordinates-y").val(location.vehicle?.coordinates?.y || "");
div.find(".vehicle-coordinates-z").val(location.vehicle?.coordinates?.z || "");
div.find(".vehicle-heading").val(location.vehicle?.heading || "");
div.find(".choose-vehicle-coordinates-btn").click(async function() {
const model = div.find(".vehicle-model").val();
const data = await placeEntity(model, "vehicle");
if(!data) return;
div.find(".vehicle-coordinates-x").val(data.coords.x);
div.find(".vehicle-coordinates-y").val(data.coords.y);
div.find(".vehicle-coordinates-z").val(data.coords.z);
div.find(".vehicle-heading").val(data.heading);
});
// Text
div.find(".text-coordinates-x").val(location.text?.coordinates?.x || "");
div.find(".text-coordinates-y").val(location.text?.coordinates?.y || "");
div.find(".text-coordinates-z").val(location.text?.coordinates?.z || "");
div.find(".choose-text-coordinates-btn").click(async function() {
const data = await placeEntity();
if(!data) return;
div.find(".text-coordinates-x").val(data.x);
div.find(".text-coordinates-y").val(data.y);
div.find(".text-coordinates-z").val(data.z);
});
$("#auction-locations-list").append(div);
}
$("#add-auction-location-btn").click(function() {
addAuctionLocation();
})
function getAuctionLocations() {
let locations = [];
$("#auction-locations-list").find(".auction-location").each(function() {
let location = {
broker: {
model: $(this).find(".broker-model").val(),
coordinates: {
x: parseFloat( $(this).find(".broker-coordinates-x").val() ),
y: parseFloat( $(this).find(".broker-coordinates-y").val() ),
z: parseFloat( $(this).find(".broker-coordinates-z").val() ),
},
heading: parseFloat( $(this).find(".broker-heading").val() ),
},
vehicle: {
model: $(this).find(".vehicle-model").val(),
coordinates: {
x: parseFloat( $(this).find(".vehicle-coordinates-x").val() ),
y: parseFloat( $(this).find(".vehicle-coordinates-y").val() ),
z: parseFloat( $(this).find(".vehicle-coordinates-z").val() ),
},
heading: parseFloat( $(this).find(".vehicle-heading").val() ),
},
text: {
coordinates: {
x: parseFloat( $(this).find(".text-coordinates-x").val() ),
y: parseFloat( $(this).find(".text-coordinates-y").val() ),
z: parseFloat( $(this).find(".text-coordinates-z").val() ),
}
}
}
locations.push(location);
});
return locations;
}
function addAuctionItem(item={}) {
const div = $(`
<div class="auction-item d-flex justify-content-center align-items-center gap-3 my-3">
<button type="button" class="btn py-0 btn-close my-auto me-2"></button>
<div class="form-floating">
<input type="number" class="form-control item-quantity" placeholder="...">
<label>${getLocalizedText("menu:quantity")}</label>
</div>
<div class="form-floating" data-bs-toggle="tooltip" data-bs-placement="top" title="${getLocalizedText("menu:base_price:info")}">
<input type="number" class="form-control item-base-price" placeholder="...">
<label>${getLocalizedText("menu:base_price")}</label>
</div>
</div>
</div>
`);
const objectDiv = createDivForObjectChoose(item);
div.find(".btn-close").after(objectDiv);
div.find(".btn-close").click(function() {
div.remove();
});
div.find("[data-bs-toggle=tooltip]").tooltip();
div.find(".item-quantity").val(item.quantity || 10);
div.find(".item-base-price").val(item.basePrice || 1000);
setChoosenObject(objectDiv, item);
$("#auction-items-list").append(div);
}
$("#add-auction-item-btn").click(function() {
addAuctionItem();
});
function getAuctionItems() {
let items = [];
$("#auction-items-list").find(".auction-item").each(function() {
const object = getChoosenObject($(this));
let item = {
quantity: parseInt( $(this).find(".item-quantity").val() ),
basePrice: parseInt( $(this).find(".item-base-price").val() ),
name: object.name,
label: object.label,
type: object.type,
}
items.push(item);
});
return items;
}
$("#auction-blip-btn").click(async function() {
const oldBlipData = $(this).data("blipData");
const blipData = await blipDialog(oldBlipData);
$(this).data("blipData", blipData);
});
// [[ NEXUS ]]
const voteInstanceRater = raterJs({
starSize: 35,
element: document.querySelector("#vote-instance-rater"),
rateCallback: async function rateCallback(rating, done) {
const instanceId = $("#nexus-modal").data("instance").id;
const success = await $.post(`https://${resName}/nexus/rateInstance`, JSON.stringify({rating, instanceId}));
if(success) voteInstanceRater.setRating(rating);
done();
}
});
const averageInstanceVotes = raterJs({
starSize: 20,
readOnly: true,
element: document.querySelector("#nexus-modal-instance-average-rating"),
});
$("#nexus-import-instance-btn").click(async function() {
const instance = $("#nexus-modal").data("instance");
const id = instance.id;
const mappedItemsNames = await itemsMapperDialog(instance.requiredItemsNames);
if(!mappedItemsNames) return;
const response = await $.post(`https://${resName}/nexus/importInstance`, JSON.stringify({id, mappedItemsNames}));
$("#nexus-modal").modal("hide");
if(response === true) reloadAllData();
showServerResponse(response);
});
let nexusDataTable = $("#nexus-table").DataTable({
"lengthMenu": [5, 10, 15, 20],
"pageLength": 10,
"order": [[4, 'desc'], [5, 'desc']],
"createdRow": function (row, data, index) {
$(row).addClass("clickable");
$(row).click(function () {
const instance = $(this).data("instance");
showInstance(instance);
$("#nexus-modal").modal("show");
});
},
"columnDefs": [{ "defaultContent": "???", "targets": "_all" }]
});
function showInstance(instance) {
$("#nexus-modal").data("instance", instance);
$("#nexus-modal-instance-listing-label").text(instance.label);
$("#nexus-instance-content-type").text(instance.type);
$("#nexus-instance-content-amount").text(instance.content.length);
$("#nexus-modal-instance-description").text(instance.description || getLocalizedText("menu:nexus:no_description"));
$("#nexus-modal-instance-author").text(instance.author);
// Content names and labels
$("#nexus-modal-instance-content").empty();
instance.content.forEach(content => {
$("#nexus-modal-instance-content").append(`
<li class="list-group-item">${content.label || content.name}</li>
`);
});
// Votes
if(instance?.votes?.total > 0) {
averageInstanceVotes.setRating(instance?.votes.averageRating);
} else {
averageInstanceVotes.setRating(0);
}
$("#nexus-modal-instance-total-votes").text(instance.votes?.total || 0);
// This server vote
voteInstanceRater.setRating(0);
}
$("#upload-to-nexus-btn").click(async function() {
const type = await listDialog(getLocalizedText("menu:nexus:data_to_share"), getLocalizedText("menu:search"), [
{value: "harvestable_item", label: getLocalizedText("menu:harvestable_item")},
{value: "drug_field", label: getLocalizedText("menu:drug_field")},
{value: "crafting_recipe", label: getLocalizedText("menu:crafting_recipe")},
{value: "laboratory", label: getLocalizedText("menu:laboratory")},
]);
if(!type) return;
let dataToChooseFrom = [];
// Depending on the type, we retrieve the possible data and create a multiSelectDialog
switch(type) {
case "harvestable_item":
dataToChooseFrom = await $.post(`https://${resName}/getAllHarvestableItems`);
break;
case "drug_field":
dataToChooseFrom = await $.post(`https://${resName}/getAllDrugsFields`);
break;
case "crafting_recipe":
dataToChooseFrom = await $.post(`https://${resName}/getAllCraftingRecipes`);
break;
case "laboratory":
dataToChooseFrom = await $.post(`https://${resName}/getAllLaboratories`);
break;
}
if(!dataToChooseFrom) return;
let elements = [];
Object.values(dataToChooseFrom).forEach(data => {
elements.push({
value: data.id,
label: data.id + " - " + (data.label || data.name)
});
})
const selectedData = await multiSelectDialog(getLocalizedText("menu:nexus:data_to_share"), getLocalizedText("menu:search"), elements);
if(!selectedData) return;
$("#nexus-modal-upload").data("selectedData", selectedData);
$("#nexus-modal-upload").data("dataType", type);
$("#nexus-upload-label").val("");
$("#nexus-upload-description").val("");
$("#nexus-upload-accept-tos").prop("checked", false);
$("#nexus-modal-upload").modal("show");
});
$("#nexus-upload-form").submit(async function(event) {
if(isThereAnyErrorInForm(event)) return;
const dataToUpload = {
type: $("#nexus-modal-upload").data("dataType"),
ids: $("#nexus-modal-upload").data("selectedData"),
label: $("#nexus-upload-label").val(),
description: $("#nexus-upload-description").val(),
}
const result = await $.post(`https://${resName}/nexus/uploadData`, JSON.stringify(dataToUpload));
if(result == true) {
swal("Success", getLocalizedText("menu:nexus:upload_success"), "success");
resetNexus();
} else {
swal("Error", result, "error");
}
$("#nexus-modal-upload").modal("hide");
});
$("#enter-in-nexus-btn").click(async function() {
$("#nexus-login").find(".spinner-border").show();
$("#enter-in-nexus-label").text("...");
const sharedData = await $.get(`https://${resName}/nexus/getSharedData`);
if(!sharedData) {
swal("Error", getLocalizedText("menu:nexus:not_available"), "error");
resetNexus();
return;
}
nexusDataTable.clear()
Object.values(sharedData).forEach(instance => {
const roundedAverageRating = instance?.votes?.averageRating ? Math.round(instance.votes.averageRating) : 0;
const ratingStars = instance?.votes?.total ? "⭐".repeat(roundedAverageRating) : getLocalizedText("menu:nexus:not_rated");
const limitedDescription = instance.description?.length > 30 ? instance.description.substring(0, 30) + "..." : instance.description;
const amount = instance.content.length;
const rawRow = nexusDataTable.row.add( [instance.label, limitedDescription, instance.type, amount, ratingStars, instance.votes?.total || 0, instance.author] );
const rowDiv = $(rawRow.node());
$(rowDiv).data("instance", instance);
})
nexusDataTable.draw();
$("#nexus-login").hide();
$("#nexus-container").show();
})
function resetNexus() {
$("#nexus-login").show();
$("#nexus-login").find(".spinner-border").hide();
$("#enter-in-nexus-label").text("Enter in Nexus");
$("#nexus-container").hide();
}
function reloadAllData() {
resetNexus();
loadHarvestableItems();
loadDrugsFields();
loadCraftingRecipes();
loadLaboratories();
loadAuctions();
}
// Open/Close menu
function openMenu(version, fullConfig) {
$("#advanced-drugs-creator-version").text(version);
$("#drugs_creator").show();
reloadAllData();
window.dispatchEvent(new Event('menuOpened'));
loadSettings(fullConfig);
}
function closeMenu() {
$("#drugs_creator").hide();
$.post(`https://${resName}/close`, {})
}
$("#close-main-btn").click(closeMenu);
function playSound(name, speed = 1.0, volume = 1.0) {
// Create an AudioContext
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
// Create the audio element
var audio = new Audio(`./assets/audio/${name}.mp3`);
audio.playbackRate = speed;
// Create a media element source
var source = audioCtx.createMediaElementSource(audio);
// Create a gain node
var gainNode = audioCtx.createGain();
gainNode.gain.value = volume; // Set the volume
// Connect the source to the gain node and the gain node to the destination
source.connect(gainNode);
gainNode.connect(audioCtx.destination);
// Play the audio
audio.play();
}
// Messages received by client
window.addEventListener('message', (event) => {
let data = event.data;
let action = data.action;
if (action == 'openMenu') {
openMenu(data.version, data.fullConfig);
} else if (action == "playSound") {
playSound(data.name, data.speed, data.volume);
}
})