From 8c5931a1425fb3af10adc49af7d317e85dba14b0 Mon Sep 17 00:00:00 2001 From: danielyxie Date: Mon, 22 Oct 2018 19:34:39 -0500 Subject: [PATCH] More rebalancing for re-worked Gang mechanic --- src/Constants.js | 1 + src/DevMenu.js | 6 +- src/Gang.js | 79 +++++++++++++--------- src/SourceFile.js | 8 +-- src/data/gangmembertasks.ts | 131 ++++++++++++++++++++++++++++++------ 5 files changed, 165 insertions(+), 60 deletions(-) diff --git a/src/Constants.js b/src/Constants.js index 5defbf59a..881204966 100644 --- a/src/Constants.js +++ b/src/Constants.js @@ -518,6 +518,7 @@ let CONSTANTS = { *** Gang Members can now be killed during territory 'warfare' *** Changed BitNode-2 Multipliers to make hacking slightly less profitable *** Gang Member Equipment + Upgrades now get cheaper as your gang grows in power and respect + *** The effects of Source-File 2 are now slightly more powerful * RAM Cost of accessing the global document object lowered from 100 GB to 25 GB * RAM Cost to use Singularity Functions outside of BitNode-4 lowered by 75%. They now only cost twice as much as they do in BitNode-4 * b1t_flum3.exe now takes significantly less time to create diff --git a/src/DevMenu.js b/src/DevMenu.js index c34b7a84b..bad2ccfd2 100644 --- a/src/DevMenu.js +++ b/src/DevMenu.js @@ -321,7 +321,6 @@ export function createDevMenu() { const bladeburnerGainRankInput = createElement("input", { class: "text-input", - display: "block", placeholder: "Rank to gain (or negative to lose rank)", type: "number", }); @@ -336,7 +335,6 @@ export function createDevMenu() { exceptionAlert(`Failed to change Bladeburner Rank in dev menu: ${e}`); } }, - display: "block", innerText: "Gain Bladeburner Rank", }); @@ -345,7 +343,6 @@ export function createDevMenu() { const gangStoredCyclesInput = createElement("input", { class: "text-input", - display: "block", placeholder: "# Cycles to add", type: "number", }); @@ -360,7 +357,6 @@ export function createDevMenu() { exceptionAlert(`Failed to add stored cycles to gang mechanic: ${e}`); } }, - display: "block", innerText: "Add cycles to Gang mechanic", }) @@ -418,9 +414,11 @@ export function createDevMenu() { devMenuContainer.appendChild(bladeburnerHeader); devMenuContainer.appendChild(bladeburnerGainRankInput); devMenuContainer.appendChild(bladeburnerGainRankButton); + devMenuContainer.appendChild(createElement("br")); devMenuContainer.appendChild(gangHeader); devMenuContainer.appendChild(gangStoredCyclesInput); devMenuContainer.appendChild(gangAddStoredCycles); + devMenuContainer.appendChild(createElement("br")); const entireGameContainer = document.getElementById("entire-game-container"); if (entireGameContainer == null) { diff --git a/src/Gang.js b/src/Gang.js index 82f507016..7baabe31c 100644 --- a/src/Gang.js +++ b/src/Gang.js @@ -390,8 +390,7 @@ Gang.prototype.processExperienceGains = function(numCycles=1) { Gang.prototype.calculatePower = function() { var memberTotal = 0; for (var i = 0; i < this.members.length; ++i) { - if (this.members[i].task instanceof GangMemberTask && - this.members[i].task.name == "Territory Warfare") { + if (GangMemberTasks.hasOwnProperty(this.members[i].task) && this.members[i].task == "Territory Warfare") { const gain = this.members[i].calculatePower(); memberTotal += gain; } @@ -413,7 +412,7 @@ Gang.prototype.clash = function(won=false) { const member = this.members[i]; // Only members assigned to Territory Warfare can die - if (member.task.name !== "Territory Warfare") { continue; } + if (member.task !== "Territory Warfare") { continue; } // Chance to die is decreased based on defense const modifiedDeathChance = baseDeathChance / Math.pow(def, 0.25); @@ -541,7 +540,7 @@ Reviver.constructors.Gang = Gang; /*** Gang Member object ***/ function GangMember(name) { this.name = name; - this.task = GangMemberTasks["Unassigned"]; //GangMemberTask object + this.task = "Unassigned"; //GangMemberTask object this.earnedRespect = 0; @@ -592,32 +591,38 @@ GangMember.prototype.updateSkillLevels = function() { } GangMember.prototype.calculatePower = function() { - return (this.hack + this.str + this.def + - this.dex + this.agi + this.cha) / 100; + return (this.hack + this.str + this.def + this.dex + this.agi + this.cha) / 95; } GangMember.prototype.assignToTask = function(taskName) { if (GangMemberTasks.hasOwnProperty(taskName)) { - this.task = GangMemberTasks[taskName]; + this.task = taskName; return true; } else { - this.task = GangMemberTasks["Unassigned"]; + this.task = "Unassigned"; return false; } } GangMember.prototype.unassignFromTask = function() { - if (GangMemberTasks.hasOwnProperty("Unassigned")) { - this.task = GangMemberTasks["Unassigned"]; - } else { - console.log("ERROR: Can't find Unassigned Gang member task"); - this.task = null; + this.task = "Unassigned"; +} + +GangMember.prototype.getTask = function() { + // Backwards compatibility + if (this.task instanceof GangMemberTask) { + this.task = this.task.name; } + + if (GangMemberTasks.hasOwnProperty(this.task)) { + return GangMemberTasks[this.task]; + } + return GangMemberTasks["Unassigned"]; } //Gains are per cycle GangMember.prototype.calculateRespectGain = function(gang) { - var task = this.task; + const task = this.getTask(); if (task == null || !(task instanceof GangMemberTask) || task.baseRespect === 0) {return 0;} var statWeight = (task.hackWeight/100) * this.hack + (task.strWeight/100) * this.str + @@ -625,16 +630,16 @@ GangMember.prototype.calculateRespectGain = function(gang) { (task.dexWeight/100) * this.dex + (task.agiWeight/100) * this.agi + (task.chaWeight/100) * this.cha; - statWeight -= (3.5 * task.difficulty); + statWeight -= (4 * task.difficulty); if (statWeight <= 0) { return 0; } - var territoryMult = AllGangs[gang.facName].territory; - if (territoryMult <= 0) { return 0; } + const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.respect) / 100; + if (isNaN(territoryMult) || territoryMult <= 0) { return 0; } var respectMult = gang.getWantedPenalty(); return 12 * task.baseRespect * statWeight * territoryMult * respectMult; } GangMember.prototype.calculateWantedLevelGain = function(gang) { - var task = this.task; + const task = this.getTask(); if (task == null || !(task instanceof GangMemberTask) || task.baseWanted === 0) {return 0;} var statWeight = (task.hackWeight/100) * this.hack + (task.strWeight/100) * this.str + @@ -643,9 +648,9 @@ GangMember.prototype.calculateWantedLevelGain = function(gang) { (task.agiWeight/100) * this.agi + (task.chaWeight/100) * this.cha; statWeight -= (3.5 * task.difficulty); - if (statWeight <= 0) {return 0;} - var territoryMult = AllGangs[gang.facName].territory; - if (territoryMult <= 0) {return 0;} + if (statWeight <= 0) { return 0; } + const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.wanted) / 100; + if (isNaN(territoryMult) || territoryMult <= 0) { return 0; } if (task.baseWanted < 0) { return task.baseWanted * statWeight * territoryMult; } else { @@ -654,7 +659,7 @@ GangMember.prototype.calculateWantedLevelGain = function(gang) { } GangMember.prototype.calculateMoneyGain = function(gang) { - var task = this.task; + const task = this.getTask(); if (task == null || !(task instanceof GangMemberTask) || task.baseMoney === 0) {return 0;} var statWeight = (task.hackWeight/100) * this.hack + (task.strWeight/100) * this.str + @@ -663,15 +668,15 @@ GangMember.prototype.calculateMoneyGain = function(gang) { (task.agiWeight/100) * this.agi + (task.chaWeight/100) * this.cha; statWeight -= (3.5 * task.difficulty); - if (statWeight <= 0) {return 0;} - var territoryMult = AllGangs[gang.facName].territory; - if (territoryMult <= 0) {return 0;} + if (statWeight <= 0) { return 0; } + const territoryMult = Math.pow(AllGangs[gang.facName].territory * 100, task.territory.money) / 100; + if (isNaN(territoryMult) || territoryMult <= 0) { return 0; } var respectMult = gang.getWantedPenalty(); return 5 * task.baseMoney * statWeight * territoryMult * respectMult; } GangMember.prototype.gainExperience = function(numCycles=1) { - var task = this.task; + const task = this.getTask(); if (task == null || !(task instanceof GangMemberTask)) {return;} this.hack_exp += (task.hackWeight / 1500) * task.difficulty * numCycles; this.str_exp += (task.strWeight / 1500) * task.difficulty * numCycles; @@ -826,8 +831,18 @@ function GangMemberTask(name="", desc="", isHacking=false, isCombat=false, this.agiWeight = params.agiWeight ? params.agiWeight : 0; this.chaWeight = params.chaWeight ? params.chaWeight : 0; + if (Math.round(this.hackWeight + this.strWeight + this.defWeight + this.dexWeight + this.agiWeight + this.chaWeight) != 100) { + throw new Error(`GangMemberTask ${this.name} weights do not add up to 100`); + } + // 1 - 100 this.difficulty = params.difficulty ? params.difficulty : 1; + + // Territory Factors. Exponential factors that dictate how territory affects gains + // Formula: Territory Mutiplier = (Territory * 100) ^ factor / 100 + // So factor should be > 1 if something should scale exponentially with territory + // and should be < 1 if it should have diminshing returns + this.territory = params.territory ? params.territory : {money: 1, respect: 1, wanted: 1}; } GangMemberTask.prototype.toJSON = function() { @@ -937,7 +952,7 @@ Gang.prototype.createGangMemberUpgradeBox = function(player, initialFilter="") { var filter = UIElems.gangMemberUpgradeBoxFilter.value.toString(); for (var i = 0; i < this.members.length; ++i) { - if (this.members[i].name.indexOf(filter) > -1 || this.members[i].task.name.indexOf(filter) > -1) { + if (this.members[i].name.indexOf(filter) > -1 || this.members[i].task.indexOf(filter) > -1) { var newPanel = this.members[i].createGangMemberUpgradePanel(this, player); UIElems.gangMemberUpgradeBoxContent.appendChild(newPanel); UIElems.gangMemberUpgradeBoxElements.push(newPanel); @@ -965,7 +980,7 @@ Gang.prototype.createGangMemberUpgradeBox = function(player, initialFilter="") { var filter = UIElems.gangMemberUpgradeBoxFilter.value.toString(); for (var i = 0; i < this.members.length; ++i) { - if (this.members[i].name.indexOf(filter) > -1 || this.members[i].task.name.indexOf(filter) > -1) { + if (this.members[i].name.indexOf(filter) > -1 || this.members[i].task.indexOf(filter) > -1) { UIElems.gangMemberUpgradeBoxElements.push(this.members[i].createGangMemberUpgradePanel(this, player)); } } @@ -983,7 +998,7 @@ GangMember.prototype.createGangMemberUpgradePanel = function(gangObj, player) { }); var header = createElement("h1", { - innerText: this.name + " (" + this.task.name + ")" + innerText: this.name + " (" + this.task + ")" }); container.appendChild(header); @@ -1472,7 +1487,7 @@ Gang.prototype.displayGangMemberList = function() { const members = this.members; const filter = UIElems.gangMemberFilter.value.toString(); for (var i = 0; i < members.length; ++i) { - if (members[i].name.indexOf(filter) > -1 || members[i].task.name.indexOf(filter) > -1) { + if (members[i].name.indexOf(filter) > -1 || members[i].task.indexOf(filter) > -1) { this.createGangMemberDisplayElement(members[i]); } } @@ -1744,8 +1759,8 @@ Gang.prototype.createGangMemberDisplayElement = function(memberObj) { }); // Set initial task in selector - if (memberObj.task instanceof GangMemberTask) { - var taskName = memberObj.task.name; + if (GangMemberTasks.hasOwnProperty(memberObj.task)) { + var taskName = memberObj.task; var taskIndex = 0; for (let i = 0; i < tasks.length; ++i) { if (taskName === tasks[i]) { diff --git a/src/SourceFile.js b/src/SourceFile.js index fb4861af2..76df6b19b 100644 --- a/src/SourceFile.js +++ b/src/SourceFile.js @@ -27,9 +27,9 @@ function initSourceFiles() { "Level 3: 28%"); SourceFiles["SourceFile2"] = new SourceFile(2, "This Source-File increases the player's crime success rate, crime money, and charisma " + "multipliers by:

" + - "Level 1: 20%
" + - "Level 2: 30%
" + - "Level 3: 35%"); + "Level 1: 24%
" + + "Level 2: 36%
" + + "Level 3: 42%"); SourceFiles["SourceFile3"] = new SourceFile(3,"This Source-File lets you create corporations on other BitNodes (although " + "some BitNodes will disable this mechanic). This Source-File also increases your charisma and company salary multipliers by:
" + "Level 1: 8%
" + @@ -126,7 +126,7 @@ function applySourceFile(srcFile) { case 2: //Rise of the Underworld var mult = 0; for (var i = 0; i < srcFile.lvl; ++i) { - mult += (20 / (Math.pow(2, i))); + mult += (24 / (Math.pow(2, i))); } var incMult = 1 + (mult / 100); Player.crime_money_mult *= incMult; diff --git a/src/data/gangmembertasks.ts b/src/data/gangmembertasks.ts index 4f759bc1b..de8b8c91c 100644 --- a/src/data/gangmembertasks.ts +++ b/src/data/gangmembertasks.ts @@ -42,6 +42,7 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [ isCombat: true, isHacking: true, name: "Unassigned", + params: {hackWeight: 100}, // This is just to get by the weight check in the GangMemberTask constructor }, { desc: "Assign this gang member to create and distribute ransomware

Earns money - Slightly increases respect - Slightly increases wanted level", @@ -55,49 +56,49 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [ isCombat: false, isHacking: true, name: "Phishing", - params: {baseRespect: 0.00008, baseWanted: 0.001, baseMoney: 2.5, hackWeight: 85, chaWeight: 15, difficulty: 3}, + params: {baseRespect: 0.00008, baseWanted: 0.001, baseMoney: 2.5, hackWeight: 85, chaWeight: 15, difficulty: 3.5}, }, { desc: "Assign this gang member to attempt identity theft

Earns money - Increases respect - Increases wanted level", isCombat: false, isHacking: true, name: "Identity Theft", - params: {baseRespect: 0.0001, baseWanted: 0.01, baseMoney: 6, hackWeight: 80, chaWeight: 20, difficulty: 4}, + params: {baseRespect: 0.0001, baseWanted: 0.01, baseMoney: 6, hackWeight: 80, chaWeight: 20, difficulty: 5}, }, { desc: "Assign this gang member to carry out DDoS attacks

Increases respect - Increases wanted level", isCombat: false, isHacking: true, name: "DDoS Attacks", - params: {baseRespect: 0.0004, baseWanted: 0.05, hackWeight: 100, difficulty: 7}, + params: {baseRespect: 0.0004, baseWanted: 0.05, hackWeight: 100, difficulty: 8}, }, { desc: "Assign this gang member to create and distribute malicious viruses

Increases respect - Increases wanted level", isCombat: false, isHacking: true, name: "Plant Virus", - params: {baseRespect: 0.0006, baseWanted: 0.05, hackWeight: 100, difficulty: 10}, + params: {baseRespect: 0.0006, baseWanted: 0.05, hackWeight: 100, difficulty: 12}, }, { desc: "Assign this gang member to commit financial fraud and digital counterfeiting

Earns money - Slightly increases respect - Slightly increases wanted level", isCombat: false, isHacking: true, name: "Fraud & Counterfeiting", - params: {baseRespect: 0.0005, baseWanted: 0.1, baseMoney: 15, hackWeight: 80, chaWeight: 20, difficulty: 17}, + params: {baseRespect: 0.0005, baseWanted: 0.1, baseMoney: 15, hackWeight: 80, chaWeight: 20, difficulty: 20}, }, { desc: "Assign this gang member to launder money

Earns money - Increases respect - Increases wanted level", isCombat: false, isHacking: true, name: "Money Laundering", - params: {baseRespect: 0.0006, baseWanted: 0.2, baseMoney: 40, hackWeight: 75, chaWeight: 25, difficulty: 20}, + params: {baseRespect: 0.0006, baseWanted: 0.2, baseMoney: 40, hackWeight: 75, chaWeight: 25, difficulty: 25}, }, { desc: "Assign this gang member to commit acts of cyberterrorism

Greatly increases respect - Greatly increases wanted level", isCombat: false, isHacking: true, name: "Cyberterrorism", - params: {baseRespect: 0.001, baseWanted: 0.5, hackWeight: 80, chaWeight: 20, difficulty: 33}, + params: {baseRespect: 0.001, baseWanted: 0.5, hackWeight: 80, chaWeight: 20, difficulty: 36}, }, { desc: "Assign this gang member to be an ethical hacker for corporations

Earns money - Lowers wanted level", @@ -111,63 +112,150 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [ isCombat: true, isHacking: false, name: "Mug People", - params: {baseRespect: 0.00005, baseWanted: 0.00001, baseMoney: 1, strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 10, chaWeight: 15, difficulty: 1}, + params: { + baseRespect: 0.00005, baseWanted: 0.00001, baseMoney: 1.2, + strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 10, chaWeight: 15, + difficulty: 1, + }, }, { - desc: "Assign this gang member to sell drugs.

Earns money - Slightly increases respect - Slightly increases wanted level", + desc: "Assign this gang member to sell drugs

Earns money - Slightly increases respect - Slightly increases wanted level - Scales slightly with territory", isCombat: true, isHacking: false, name: "Deal Drugs", - params: {baseRespect: 0.00008, baseWanted: 0.001, baseMoney: 4, agiWeight: 20, dexWeight: 20, chaWeight: 60, difficulty: 3}, + params: { + baseRespect: 0.00008, baseWanted: 0.001, baseMoney: 5, + agiWeight: 20, dexWeight: 20, chaWeight: 60, + difficulty: 3.5, + territory: { + money: 1.1, + respect: 1, + wanted: 1.15, + }, + }, + }, + { + desc: "Assign this gang member to extort civilians in your territory

Earns money - Slightly increases respect - Increases wanted - Scales heavily with territory", + isCombat: true, + isHacking: false, + name: "Strongarm Civilians", + params: { + baseRespect: 0.00004, baseWanted: 0.0001, baseMoney: 2.5, + hackWeight: 10, strWeight: 25, defWeight: 25, dexWeight: 20, agiWeight: 10, chaWeight: 10, + difficulty: 5, + territory: { + money: 2, + respect: 1.1, + wanted: 1.5 + } + } }, { desc: "Assign this gang member to run cons

Earns money - Increases respect - Increases wanted level", isCombat: true, isHacking: false, name: "Run a Con", - params: {baseRespect: 0.00015, baseWanted: 0.01, baseMoney: 10, strWeight: 5, defWeight: 5, agiWeight: 25, dexWeight: 25, chaWeight: 40, difficulty: 10}, + params: { + baseRespect: 0.00015, baseWanted: 0.01, baseMoney: 12.5, + strWeight: 5, defWeight: 5, agiWeight: 25, dexWeight: 25, chaWeight: 40, + difficulty: 14, + }, }, { desc: "Assign this gang member to commit armed robbery on stores, banks and armored cars

Earns money - Increases respect - Increases wanted level", isCombat: true, isHacking: false, name: "Armed Robbery", - params: {baseRespect: 0.00015, baseWanted: 0.05, baseMoney: 25, hackWeight: 20, strWeight: 15, defWeight: 15, agiWeight: 10, dexWeight: 20, chaWeight: 20, difficulty: 17}, + params: { + baseRespect: 0.00015, baseWanted: 0.05, baseMoney: 32, + hackWeight: 20, strWeight: 15, defWeight: 15, agiWeight: 10, dexWeight: 20, chaWeight: 20, + difficulty: 20, + }, }, { - desc: "Assign this gang member to traffick illegal arms

Earns money - Increases respect - Increases wanted level", + desc: "Assign this gang member to traffick illegal arms

Earns money - Increases respect - Increases wanted level - Scales heavily with territory", isCombat: true, isHacking: false, name: "Traffick Illegal Arms", - params: {baseRespect: 0.0003, baseWanted: 0.1, baseMoney: 40, hackWeight: 15, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 75, difficulty: 25}, + params: { + baseRespect: 0.0003, baseWanted: 0.1, baseMoney: 50, + hackWeight: 15, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 25, + difficulty: 28, + territory: { + money: 1.2, + respect: 1.3, + wanted: 1.25, + }, + }, }, { desc: "Assign this gang member to threaten and black mail high-profile targets

Earns money - Slightly increases respect - Slightly increases wanted level", isCombat: true, isHacking: false, name: "Threaten & Blackmail", - params: {baseRespect: 0.0002, baseWanted: 0.05, baseMoney: 15, hackWeight: 25, strWeight: 25, dexWeight: 25, chaWeight: 25, difficulty: 28}, + params: { + baseRespect: 0.0002, baseWanted: 0.05, baseMoney: 20, + hackWeight: 25, strWeight: 25, dexWeight: 25, chaWeight: 25, + difficulty: 28, + }, }, { - desc: "Assign this gang member to commit acts of terrorism

Greatly increases respect - Greatly increases wanted level", + desc: "Assign this gang member to engage in human trafficking operations

Earns money - Increases respect - Increases wanted level - Scales heavily with territory", + isCombat: true, + isHacking: false, + name: "Human Trafficking", + params: { + baseRespect: 0.005, baseWanted: 0.2, baseMoney: 100, + hackWeight: 30, strWeight: 5, defWeight: 5, dexWeight: 30, chaWeight: 30, + difficulty: 36, + territory: { + money: 1.5, + respect: 1.5, + wanted: 1.6, + } + } + }, + { + desc: "Assign this gang member to commit acts of terrorism

Greatly increases respect - Greatly increases wanted level - Scales heavily with territory", isCombat: true, isHacking: false, name: "Terrorism", - params: {baseRespect: 0.001, baseWanted: 1, hackWeight: 20, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 20, difficulty: 33}, + params: { + baseRespect: 0.001, baseWanted: 1, + hackWeight: 20, strWeight: 20, defWeight: 20, dexWeight: 20, chaWeight: 20, + difficulty: 36, + territory: { + money: 1, + respect: 2, + wanted: 2, + }, + }, }, { desc: "Assign this gang member to be a vigilante and protect the city from criminals

Decreases wanted level", isCombat: true, isHacking: true, name: "Vigilante Justice", - params: {baseWanted: -0.001, hackWeight: 20, strWeight: 20, defWeight: 20, dexWeight: 20, agiWeight: 20, difficulty: 1}, + params: { + baseWanted: -0.001, + hackWeight: 20, strWeight: 20, defWeight: 20, dexWeight: 20, agiWeight: 20, + difficulty: 1, + territory: { + money: 1, + respect: 1, + wanted: 0.9, // Gets harder with more territory + }, + }, }, { desc: "Assign this gang member to increase their combat stats (str, def, dex, agi)", isCombat: true, isHacking: true, name: "Train Combat", - params: {strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 25, difficulty: 5}, + params: { + strWeight: 25, defWeight: 25, dexWeight: 25, agiWeight: 25, + difficulty: 5 + }, }, { desc: "Assign this gang member to train their hacking skills", @@ -181,6 +269,9 @@ export const gangMemberTasksMetadata: IGangMemberTaskMetadata[] = [ isCombat: true, isHacking: true, name: "Territory Warfare", - params: {hackWeight: 15, strWeight: 20, defWeight: 20, dexWeight: 20, agiWeight: 20, chaWeight: 5, difficulty: 3}, + params: { + hackWeight: 15, strWeight: 20, defWeight: 20, dexWeight: 20, agiWeight: 20, chaWeight: 5, + difficulty: 3 + }, }, ];