3569 lines
		
	
	
		
			No EOL
		
	
	
		
			123 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			3569 lines
		
	
	
		
			No EOL
		
	
	
		
			123 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 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);
 | |
| 	}
 | |
| }) | 
