// ==UserScript==
// @name [TORN] OC 2.0 Helper
// @namespace Violentmonkey Scripts
// @match https://www.torn.com/*
// @version 5.3.0
// @author callmericky [3299880] / whatdoesthespacebardo
// @description Gives an overview of OC 2.0, showing members not in crimes, members in each crime, and if there are issues with any crimes. Visible when flying.
// @require http://code.jquery.com/jquery-3.6.0.min.js
// @connect tornprobability.com
// @connect tornprobability.com:3000
// @grant GM_registerMenuCommand
// @grant GM.setValue
// @grant GM.getValue
// @grant GM.xmlHttpRequest
// @license GNU GPLv3
// ==/UserScript==
//IF DROPDOWN MENU DOESN'T WORK, MANUALLY ADD YOUR API KEY HERE
var APIKey = "";
const PDA_APIKey = "###PDA-APIKEY###"
/*
* STOP CHANGING THINGS FROM HERE
*/
//fix for tampermonkey
var $ = window.jQuery;
//fix for tornPDA
const XHR = GM.xmlHttpRequest || GM.xmlhttpRequest;
let memberInfo = {};
let pageURL = $(location).attr("href");
let totalMembers = 0;
let availableMembers = 0;
let soonAvailableMembers = 0;
let soonAvailableMembersList = []
let activeMembers = 0;
let userInfo = {};
let crimeListUninitiated = []
let crimeListRecruiting = []
let crimeListPlanning = []
let crimeIDListUninitiated = []
let crimeIDListRecruiting = []
let crimeIDListFull = []
let crimeList = []
let availMemberList = []
let myAPIData = null
let itemIDObj = {}
let containerMaxWidth = "784px"
let containerBigMaxWidth = "976px"
let userSettings = {}
let displayIgnoreList = []
let skippedMemberCount = 0
let skippedMemberList = []
let availableCrimeSlots = {}
let availableCrimeSlotsCount = 0
let tornProbabilityAvailableScenarios = []
let calculatedSuccessChanceObj = {}
let alreadyGotItemKeys = false
var OC2_timerID = null
const crimeListShowText = (`[ Show all ]`)
const crimeListHideText = (`[ Hide all ]`)
const crimeButtonShowText = (`Show Crimes`)
const crimeButtonHideText = (`Hide Crimes`)
const lazyMembersButtonShowText = (`Show Members`)
const lazyMembersButtonHideText = (`Hide Members`)
const settingsButtonAscText = (`▴`) //▴ ▴
const settingsButtonDescText = (`▾`) // ▾ ▾
var crimeItemIcon = (`🛠`) //🛠 🛠
if (isPDA()) {
crimeItemIcon = (`⚒`) // ⚒ $#9874;
}
const defaultUserSettings = {
"memberShow": "member-show",
"crimesShow": "crimes-hide",
"sortType": "time-asc", //time-asc / time-desc / level-asc / level-desc
"memberSort": "OC-desc", //OC-asc / OC-desc / active-asc / active-desc
"lastOC_yellow": 24,
"lastOC_red": 48,
"lastActivity": 96,
"memberIgnoreList": [],
"showSidebarOC": "sidebar-show",
"showNegativeTimes": "negative-timer-hide",
"warnMissingItems": "warn-items-show",
"warnLowSuccess": "warn-success-show",
"warnLowSuccessPercentage": 50,
"showProgressPercentage": "progress-show",
"showSelfSuccessRate": "self-success-show",
"showAllenoneScript": "torn-probability-show",
"memberDirectionality": "row-column", // row-column / column-row
}
let _isWindowNormal = window.matchMedia("(min-width: 1000px)")
let _isWindowSmallish = window.matchMedia("(min-width: 785px)")
let _isWindowSmall = window.matchMedia("(max-width: 784px) and (min-width: 387px)")
let _isWindowTiny = window.matchMedia("(max-width: 386px)")
let colorObj = {
"normal_font": {
"darkmode": "rgb(221, 221, 221)",
"lightmode": "rgb(51, 51, 51)"
},
"dark_bg_link": {
"darkmode": "rgb(116, 192, 252)",
"lightmode": "rgb(116, 192, 252)"
},
"link": {
"darkmode": "rgb(116, 192, 252)",
"lightmode": "#006699"
},
"recolor": {
"green": {
"darkmode": "rgb(130, 201, 30)",
"lightmode": "rgb(92, 148, 13)"
},
"yellow": {
"darkmode": "rgb(252, 196, 25)",
"lightmode": "rgb(199, 139, 7)"
},
"red": {
"darkmode": "rgb(255, 135, 135)",
"lightmode": "rgb(224, 49, 49)"
},
"blue": {
"darkmode": "rgb(59, 201, 219)",
"lightmode": "rgb(12, 133, 153)"
}
},
"userindicatorbg": {
"darkmode": "rgb(63, 68, 45)",
"lightmode": "rgb(238, 241, 228)"
},
"footerbg": {
"darkmode": "rgb(51, 51, 51)",
"lightmode": "rgb(242, 242, 242)"
},
"crimeselectbg": {
"darkmode": "rgba(0,0,0,0.2)",
"lightmode": "rgba(150,150,150,0.1)"
},
"fancyBg": {
"darkmode": "inherit",
"lightmode": "#fff"
},
"crimeIcon": {
"default": {
"lightmode": "rgba(100, 100, 100, 0.5)",
"darkmode": "rgba(221, 221, 221, 0.5)"
},
"highlightYellow": {
"lightmode": "rgb(230, 180, 0)",
"darkmode": "rgba(252, 196, 25, 0.8)"
},
"highlightRed": {
"lightmode": "rgba(255, 135, 135, 1)",
"darkmode": "rgba(255, 135, 135, 0.8)"
},
"highlightVeryRed": {
"lightmode": "rgba(200, 33, 33, 1)",
"darkmode": "rgba(200, 33, 33, 1)"
}
},
"buttons": {
"background": {
"lightmode": "linear-gradient(rgb(255, 255, 255) 0%, rgb(221, 221, 221) 100%)",
"darkmode": "linear-gradient(rgb(85, 85, 85) 0%, rgb(51, 51, 51) 100%)"
},
"textcolor": {
"lightmode": "rgb(102, 102, 102)",
"darkmode": "rgb(221, 221, 221)"
},
"hovercolor": {
"lightmode": "rgb(200,200,200)",
"darkmode": "rgb(28,28,28)"
}
}
}
let colorDisplayMode = $("body#body").hasClass("dark-mode") ? "darkmode" : "lightmode"
var membersButtonShowText = (`<svg width="8px" height="8px" viewBox="1 1 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><polygon class="OC2-triangle" points="13,8 5,16 5,0" fill="${colorObj.normal_font[colorDisplayMode]}" /></svg>`) //⏵ ⏵ <svg width="11px" height="11x" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><rect width="16" height="16" id="icon-bound" fill="none" /><polygon points="13,8 5,16 5,0" /></svg>
var membersButtonHideText = (`<svg width="8px" height="8px" viewBox="1 1 13 13" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><polygon class="OC2-triangle" points="8,13 0,5 16,5" fill="${colorObj.normal_font[colorDisplayMode]}" /></svg>`) //⏷ ⏷
var moreInfoHover = (`<svg width="14px" height="14px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10" stroke="${colorObj.link[colorDisplayMode]}" stroke-width="2"/><path d="M12 7H12.01" stroke="${colorObj.link[colorDisplayMode]}" stroke-width="2" stroke-linecap="round"/><path d="M10 11H12V16" stroke="${colorObj.link[colorDisplayMode]}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 16H14" stroke="${colorObj.link[colorDisplayMode]}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>`)
function getUserID() {
let _profileLink = $(".settings-menu > .link > a")[0]
let _matchregex = /profiles\.php.+XID=(\d+)/i
let _userID = _matchregex.exec(_profileLink)
userInfo.id = _userID[1]
return _userID[1]
}
//user settings functions
if (!isPDA()) {
const menu_command_1 = GM_registerMenuCommand("Open Settings Page", showSettingsPage)
}
function showSettingsPage() {
window.open("https://www.torn.com/OC2_Settings_Page", "_blank")
}
async function getAPIKey() {
if (isPDA()) {
APIKey = PDA_APIKey
return PDA_APIKey
} else if (APIKey != null && APIKey.length > 0) {
return APIKey
} else {
return await GM.getValue("CMR_OC2_APIKey", null)
.then(function(data) {
APIKey = data
return data
})
}
}
async function setUserSettings(_settings) {
if (!_settings) {
return
}
if (isPDA()) {
localStorage.setItem("CMR_OC2_userSettings", JSON.stringify(_settings))
if ( $("#OC2-APIInput").val().length > 5 && $("#OC2-APITestResult-Final").hasClass("color-green") ) {
localStorage.setItem("CMR_OC2_APIKey", $("#OC2-APIInput").val())
}
return
} else {
await GM.setValue("CMR_OC2_userSettings", JSON.stringify(_settings))
}
if ( $("#OC2-APIInput").val().length > 5 && $("#OC2-APITestResult-Final").hasClass("color-green") ) {
await GM.setValue("CMR_OC2_APIKey", $("#OC2-APIInput").val())
}
}
async function getUserSettings(event) {
if (isPDA()) {
if (!localStorage.getItem("CMR_OC2_userSettings")) {
userSettings = defaultUserSettings
} else {
userSettings = JSON.parse(localStorage.getItem("CMR_OC2_userSettings"))
}
return
}
return await GM.getValue("CMR_OC2_userSettings", JSON.stringify(defaultUserSettings))
.then( (data) => {
userSettings = JSON.parse(data)
})
}
//boolean logic functions
function isPDA() {
const PDATestRegex = !/^(###).+(###)$/.test(PDA_APIKey);
return PDATestRegex;
}
function checkCrimesPage() {
let pageURL = $(location).attr("href")
return ((pageURL.search("step=your") >= 0) && (pageURL.search("tab=crimes") >= 0))
}
async function checkTravelFactionPage() {
let pageURL = $(location).attr("href")
if ( ($('body').attr("data-traveling") == "true") || ($('body').attr("data-traveling") == true) || ($('body').attr("data-abroad") == "true") || ($('body').attr("data-abroad") == true) ) {
if (!myAPIData) {
try {
let _successfulGetAPIData = await getAndAnalyzeAPIData()
if (_successfulGetAPIData.error) {
$(".OC2-memberTable").hide()
$(".OC2-memberTableErrorDisplay").html(`<span style="margin-left: 20px">Error occured: ${_successfulGetAPIData.error.error}. Please visit the <a href="https://www.torn.com/OC2_Settings_Page" target="_new" style="color: inherit; font-weight: bold; text-decoration: underline">Settings Page</a> to set up an API key</span>`)
$(".OC2-memberTableErrorDisplay").show()
return _successfulGetAPIData.error
}
} catch(_err) {
return false
}
}
if (pageURL.search("ID="+myAPIData.basic.id) >= 0) {
return true;
}
}
return false;
}
//API call functions
async function getAndAnalyzeAPIData() {
console.log("OC 2.0 Overview Script: Sending API request to get OC 2.0 crime data")
return await $.ajax({
dataType: "json",
url: (`https://api.torn.com/v2/faction/basic,crimes,members?cat=available,completed&offset=0&striptags=true&comment=OC2-helper`),
headers: {
Authorization: (`ApiKey ${APIKey}`)
}
}).done( data => {
if (data.error) {
return data
}
checkMembersInCrimes(data)
myAPIData = data
return data
}).fail( (error) => {
return error
})
}
async function getItemNamesFromID(_arrayOfIds) {
console.log("OC 2.0 Overview Script: Sending API request to get item ID names")
return await $.getJSON(`https://api.torn.com/torn/${_arrayOfIds.toString()}?selections=items&key=${APIKey}&comment=OC2-helper`)
}
async function getTornProbabilityCrimeSuccess(_scenarioName, _successRates, _crimeID) {
let _payload = {
"scenario": _scenarioName,
"parameters": _successRates
}
return await XHR({
method: "POST",
responseType: "json",
headers: {
"Content-Type": "application/json"
},
timeout: 5000,
url: "https://tornprobability.com:3000/api/CalculateSuccess",
data: JSON.stringify(_payload),
onload: function(response) {
try {
if (response.status >= 200 && response.status < 300) {
calculatedSuccessChanceObj[_crimeID] = (response.response.successChance*100).toFixed(2)
let _crimeLi_originalHTML = $("li.OC2-crimeLi.OC2-crimeID_" + _crimeID + " div.OC2-tableCrime").html()
$("li.OC2-crimeLi.OC2-crimeID_" + _crimeID + " div.OC2-tableCrime").html(_crimeLi_originalHTML + ` ${(response.response.successChance*100).toFixed(2)}%`)
alreadyGotItemKeys = true
return (response.response);
} else {
return (new Error(response.error || 'Problem with API endpoint tornprobability.com - try again later'))
}
} catch (err) {
return err
}
},
onerror: (err) => {return err},
ontimeout: (err) => {return err}
});
}
async function getTornProbabilitySupportedScenarios() {
return await XHR({
method: "GET",
responseType: "json",
timeout: 5000,
url: "https://tornprobability.com:3000/api/GetSupportedScenarios",
onload: function(response) {
try {
if (response.status >= 200 && response.status < 300) {
for (let i = 0; i < response.response.length; i++) {
tornProbabilityAvailableScenarios.push(response.response[i].name)
}
return response.response;
} else {
return (new Error(response.error || 'Problem with API endpoint tornprobability.com - try again later'))
}
} catch (err) {
return err
}
},
onerror: (err) => {return err},
ontimeout: (err) => {return err}
});
}
//calculations and conversions
async function convertItemIDArrayToItems() {
let _arrayOfIDs = []
let _matchregex = /<\#(\d+)>/i
for (var _key of Object.keys(itemIDObj)) {
_arrayOfIDs.push(_key)
}
if (alreadyGotItemKeys == false) {
await getItemNamesFromID(_arrayOfIDs)
.then( (_data) => {
for (var _itemID of Object.keys(_data.items)) {
itemIDObj[_itemID].name = _data.items[_itemID].name
}
})
}
for (let i = 0; i < $(".OC2-tableCrimeMemberItem:has(*)").length; i++) {
let _oldTitle = $(".OC2-tableCrimeMemberItem:has(*)").eq(i).attr("title")
let _regexResult = _matchregex.exec($(".OC2-tableCrimeMemberItem:has(*)").eq(i).attr("title"))
let _newTitle = _oldTitle.replace(_matchregex, `${itemIDObj[_regexResult[1]].name} <$1>`)
$(".OC2-tableCrimeMemberItem:has(*)").eq(i).attr("title", _newTitle)
}
//this needs more IFs to prevent errors, since the item ID may exist but the span with the warning does not exist
for (let i = 0; i < $(".OC2-crimeMouseoverWarning").length; i++) {
let _oldTitle = $(".OC2-crimeMouseoverWarning").eq(i).attr("title")
if (_oldTitle) {
let _regexResult = _matchregex.exec($(".OC2-crimeMouseoverWarning").eq(i).attr("title"))
if (_regexResult) {
let _newTitle = _oldTitle.replace(_matchregex, `${itemIDObj[_regexResult[1]].name} <$1>`)
$(".OC2-crimeMouseoverWarning").eq(i).attr("title", _newTitle)
}
}
}
}
function timestampDiff(laterTimestamp) {
var currentTimestamp = Math.floor(Date.now()/1000)
var _returnString = ""
var timeDiff = 0
var _highlightClass = ""
if (laterTimestamp > currentTimestamp) {
timeDiff = laterTimestamp - currentTimestamp
}
if (timeDiff < 43200) { //12 hours = 12 * 60 * 60 = 43200
_highlightClass = "OC2-highlightText"
}
if ((laterTimestamp < currentTimestamp) && userSettings?.showNegativeTimes == "negative-timer-show" ) {
timeDiff = currentTimestamp - laterTimestamp
_highlightClass = "OC2-highlightRed"
}
let _d = timeDiff < 86400 ? 0 : Math.floor(timeDiff/86400)
let _h = timeDiff < 3600 ? 0 : Math.floor(timeDiff/3600) - _d*24 //24h in 1d
let _m = timeDiff < 60 ? 0 : Math.floor(timeDiff/60) - _h*60 - _d*1440 //60m in 1h, 1440m in 1d
let _s = timeDiff - _m*60 - _h*3600 - _d*86400 //60s in 1m, 3600s in 1h, 86400s in 1d
_returnString += `<span class="${_highlightClass}">${_d.toString().padStart(2,'0')}:${_h.toString().padStart(2,'0')}:${_m.toString().padStart(2,'0')}:${_s.toString().padStart(2,'0')}</span>`
return _returnString
}
function stringifyTimestampOld(_timeDiff) {
var currentTimestamp = Math.floor(Date.now()/1000)
var _returnString = ""
var _highlightClass = ""
if (_timeDiff == -1) {
return (`User has not joined the last ${myAPIData.crimes.length} OCs`)
}
let _d = _timeDiff < 86400 ? 0 : Math.floor(_timeDiff/86400)
let _h = _timeDiff < 3600 ? 0 : Math.floor(_timeDiff/3600) - _d*24 //24h in 1d
let _m = _timeDiff < 60 ? 0 : Math.floor(_timeDiff/60) - _h*60 - _d*1440 //60m in 1h, 1440m in 1d
let _s = _timeDiff - _m*60 - _h*3600 - _d*86400 //60s in 1m, 3600s in 1h, 86400s in 1d
if (_d > 0) {
_returnString += (`${_d.toString().padStart(1,'0')}d `)
}
if (_d > 0 || _h > 0) {
_returnString += (`${_h.toString().padStart(2,'0')}h `)
}
if (_d > 0 || _h > 0 || _m > 0) {
_returnString += (`${_m.toString().padStart(2,'0')}m `)
}
_returnString += (`ago`)
return _returnString
}
function timestampOldDiff(olderTimestamp) {
var currentTimestamp = Math.floor(Date.now()/1000)
if (olderTimestamp == 0) {
return -1
}
return currentTimestamp - olderTimestamp //time since last OC in seconds
}
const timerTick = () => {
let _timeList = $("span.OC2-countdown")
for (let i = 0; i < _timeList.length; i++) {
$(_timeList[i]).html(timestampDiff(parseInt($(_timeList[i]).attr("data-countdown"))))
}
OC2_timerID = setTimeout(timerTick, 1000)
$(".OC2-highlightText").css({
"color": colorObj.recolor.yellow[colorDisplayMode],
"font-weight": "bold"
})
$(".OC2-highlightRed").css({
"color": colorObj.recolor.red[colorDisplayMode],
"font-weight": "bold"
})
}
//the nitty gritty functions
function checkMembersInCrimes(_data) {
//put all member ids into a list
let fallenMemberCount = 0
for (let i = 0; i < (_data.members).length; i++) {
if (_data.members[i].status.state == "Fallen") {
fallenMemberCount += 1;
//skip fallen members
} else if (_data.members[i].position == "Recruit") {
//skip recruits since they can't join OC
skippedMemberCount += 1;
skippedMemberList.push({
"id": _data.members[i].id,
"name": _data.members[i].name
})
} else {
memberInfo[_data.members[i].id] = {
"id": _data.members[i].id,
"name": _data.members[i].name,
"last_action": _data.members[i].last_action,
"statusDesc": _data.members[i].status.description,
"status": _data.members[i].status.state,
"lastCrime": 0
}
if (userSettings.memberIgnoreList.includes((_data.members[i].id).toString())) {
//remember to subtract ignored members
skippedMemberCount += 1;
}
}
}
totalMembers = (_data.members).length - skippedMemberCount - fallenMemberCount;
activeMembers = 0 //if this doesn't reset, hashchange will cause the following part to re-fire and get activemembers count wrong.
//go through crime list
for (let i = 0; i < (_data.crimes).length; i++) {
if (_data.crimes[i].status == 'Expired') {
continue; //skip all expired crimes
}
//sort members into objects
if ((_data.crimes[i].status == 'Successful' || _data.crimes[i].status == 'Failure')) { //for crimes that were complete, put that crime's info to member's last crime completed section
for (let j=0; j<(_data.crimes[i].slots).length; j++) {
//skip slots that are empty
if (_data.crimes[i].slots[j].user) {
//ignore members that left the faction
if (!memberInfo[_data.crimes[i].slots[j].user.id]) {
continue
}
let _lastCrimeTime = _data.crimes[i].ready_at
if (_data.crimes[i].executed_at) { //take into account executed at, for crimes that were stalled
_lastCrimeTime = _data.crimes[i].executed_at
}
if (memberInfo[_data.crimes[i].slots[j].user.id].lastCrime < _lastCrimeTime) { //make sure that the lastCrime in the memberInfo is the latest crime so far
memberInfo[_data.crimes[i].slots[j].user.id].lastCrime = _lastCrimeTime
}
}
}
}
if ((_data.crimes[i].status == 'Recruiting' || _data.crimes[i].status == 'Planning')) {
if (_data.crimes[i].planning_at) { //if crime is not initiated, it will be null for planning_at. No point looking for members because there won't be any.
for (let k=0; k<(_data.crimes[i].slots).length; k++) {
if (_data.crimes[i].slots[k].user) {
//ignore members that left the faction
if (!memberInfo[_data.crimes[i].slots[k].user.id]) {
continue
}
memberInfo[_data.crimes[i].slots[k].user.id].crimeInfo = {
"crimeName": _data.crimes[i].name,
"crimeDifficulty": _data.crimes[i].difficulty,
"crimeId": _data.crimes[i].id,
"crimePosition": _data.crimes[i].slots[k].position,
"crimeSuccess": _data.crimes[i].slots[k].checkpoint_pass_rate,
"crimeProgress": _data.crimes[i].slots[k].user.progress
}
activeMembers = activeMembers + 1;
//if the crime is almost done AND it is in the planning stage, add member count to soonAvailableMembers
if ((_data.crimes[i].ready_at < (Math.floor(Date.now()/1000)+60*60*24) ) && (_data.crimes[i].status == 'Planning')) {
soonAvailableMembers = soonAvailableMembers + 1;
soonAvailableMembersList.push(_data.crimes[i].slots[k].user.id)
}
//if this is the user, then store info for later use
if ((_data.crimes[i].slots[k].user.id) == getUserID()) {
userInfo = memberInfo[_data.crimes[i].slots[k].user.id]
userInfo.crimeInfo.crimeTime = _data.crimes[i].ready_at
}
}
}
}
}
//sort crimes into arrays
//crimes in recruiting include both crimes with members (has planning_at and with no members (don't have planning_at)
if (_data.crimes[i].status == "Recruiting") {
//get crimes with no members
if (_data.crimes[i].planning_at == null) {
crimeListUninitiated.push(_data.crimes[i])
} else {
crimeListRecruiting.push(_data.crimes[i])
}
}
//crimes filled with members move to status = planning
if (_data.crimes[i].status == "Planning") {
crimeListPlanning.push(_data.crimes[i])
}
}
availableMembers = totalMembers - activeMembers;
}
function sortCrimeInfo() {
//remove all crime Lis
$(".OC2-memberViewer li.OC2-crimeLi").not(".OC2-memberViewer li[class*='OC2-titleLi']").remove()
$("li.OC2-crimeMemberLi").remove()
if (userSettings.sortType == "level-desc") {
crimeListUninitiated.sort( (a,b) => a.difficulty - b.difficulty)
crimeListRecruiting.sort( (a,b) => a.difficulty - b.difficulty)
crimeListPlanning.sort( (a,b) => a.difficulty - b.difficulty)
} else if (userSettings.sortType == "level-asc") {
crimeListUninitiated.sort( (a,b) => b.difficulty - a.difficulty)
crimeListRecruiting.sort( (a,b) => b.difficulty - a.difficulty)
crimeListPlanning.sort( (a,b) => b.difficulty - a.difficulty)
} else if (userSettings.sortType == "time-desc") { //asc and desc are swapped around. I don't know why but it works. Some math thing. I need to think about it.
crimeListUninitiated.sort( (a,b) => a.expired_at - b.expired_at)
crimeListRecruiting.sort( (a,b) => a.ready_at - b.ready_at)
crimeListPlanning.sort( (a,b) => a.ready_at - b.ready_at)
} else if (userSettings.sortType == "time-asc") {
crimeListUninitiated.sort( (a,b) => b.expired_at - a.expired_at)
crimeListRecruiting.sort( (a,b) => b.ready_at - a.ready_at)
crimeListPlanning.sort( (a,b) => b.ready_at - a.ready_at)
}
}
function sortAvailMembers() {
$("li.OC2-memberAvailable").remove()
if (userSettings.memberSort == "OC-asc") {
availMemberList.sort( (a,b) => b.lastCrime - a.lastCrime )
}
if (userSettings.memberSort == "OC-desc") {
availMemberList.sort( (a,b) => a.lastCrime - b.lastCrime )
}
if (userSettings.memberSort == "active-asc") {
availMemberList.sort( (a,b) => a.last_action.timestamp - b.last_action.timestamp )
}
if (userSettings.memberSort == "active-desc") {
availMemberList.sort( (a,b) => b.last_action.timestamp - a.last_action.timestamp )
}
}
function arrangeMemberDirectionality() {
//sort available members
if (userSettings.memberDirectionality == "column-row" && !_isWindowTiny.matches) {
let availMemberElms = $(".OC2-memberTable li.OC2-memberAvailable")
let _internalCount = 0;
for (let i = 1; i < availMemberElms.length + 1; i++) {
if (i < (availMemberElms.length / 2) + 1) {
_internalCount = _internalCount + 1
$(availMemberElms[i-1]).css("order", _internalCount*2-1)
} else {
if (_internalCount >= (availMemberElms.length / 2)) {
_internalCount = 0;
}
_internalCount = _internalCount + 1;
$(availMemberElms[i-1]).css("order", _internalCount*2)
}
}
}
}
function putAvailMembersIntoTable(_memberArray, _afterElm) {
if (_memberArray.length < 1) {
return //don't do anything if there are no members
}
let _ignoreTitleText = ""
let _skipTitleText = ""
if (skippedMemberList.length > 0) {
_skipTitleText = "Recruits unavailable for OCs:"
skippedMemberList.forEach( (_skippedMemberName) => {
_skipTitleText += (`<br />${_skippedMemberName.name} [${_skippedMemberName.id}]`)
})
}
_memberArray.forEach( (_member) => {
if (userSettings.memberIgnoreList) {
if (userSettings.memberIgnoreList.includes((_member.id).toString())) {
if (_ignoreTitleText == "") {
_ignoreTitleText = "<br />Ignored members:"
}
_ignoreTitleText += `<br />${_member.name} [${_member.id}]`
return //skip member to be ignored
}
}
let _outputHTML = ""
let _memberHighlight = "OC2-memberAvailable"
let _memberInactiveTitle = `Last Action: ${_member.last_action.relative}`
let _memberCrimeFill = colorObj.crimeIcon.default[colorDisplayMode]
let _memberNameColor = ""
if (_member.id == userInfo.id) {
_memberHighlight += " OC2-userIndicator"
}
if (_member.lastCrime == 0) { //user is not found in the crimes obtained from the API
_memberCrimeFill = colorObj.crimeIcon.highlightVeryRed[colorDisplayMode]
}
if (timestampOldDiff(_member.lastCrime) > parseInt(userSettings.lastOC_yellow) * 60 * 60) {
_memberCrimeFill = colorObj.crimeIcon.highlightYellow[colorDisplayMode]
}
if (timestampOldDiff(_member.lastCrime) > parseInt(userSettings.lastOC_red) * 60 * 60) {
_memberCrimeFill = colorObj.crimeIcon.highlightRed[colorDisplayMode]
}
if ( (Math.floor(Date.now()/1000) - _member.last_action.timestamp) > parseInt(userSettings.lastActivity) * 60 * 60) {
_memberNameColor = "inactive"
}
if ( (_member.status).toLowerCase() == "federal" ) {
_memberNameColor = "federal"
}
let _lastCrimeIcon = (`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="11" height="11" viewBox="0 0 30 40"><path fill="${_memberCrimeFill} "d="M397,168c-7.573,0-15,5.76-15,13.333,0,4.589,3.375,10.129,6.512,14.524a10.05,10.05,0,0,1,16.935-.067c3.22-4.457,6.553-9.865,6.553-14.457C412,173.76,404.573,168,397,168Zm-5.83,18.333a4.166,4.166,0,1,1,4.167-4.166A4.167,4.167,0,0,1,391.17,186.333Zm11.667,0A4.166,4.166,0,1,1,407,182.167,4.166,4.166,0,0,1,402.837,186.333ZM397,194.52a6.74,6.74,0,1,0,6.74,6.74A6.741,6.741,0,0,0,397,194.52Zm1.752,2.458a1.07,1.07,0,1,1-1.07,1.07A1.071,1.071,0,0,1,398.752,196.978Zm-3.574,0a1.07,1.07,0,1,1-1.068,1.07A1.071,1.071,0,0,1,395.178,196.978Zm-1.918,5.35a1.07,1.07,0,1,1,1.07-1.07A1.071,1.071,0,0,1,393.26,202.328Zm1.918,3.212a1.069,1.069,0,1,1,1.07-1.07A1.07,1.07,0,0,1,395.178,205.54Zm.752-4.28a1.07,1.07,0,1,1,1.07,1.07A1.071,1.071,0,0,1,395.93,201.26Zm2.822,4.28a1.069,1.069,0,1,1,1.07-1.07A1.071,1.071,0,0,1,398.752,205.54Zm1.988-3.212a1.07,1.07,0,1,1,1.07-1.07A1.069,1.069,0,0,1,400.74,202.328Z" transform="translate(-382 -168)"></path></svg>`)
_outputHTML = (`<li class="table-cell ${_memberHighlight}">
<div class="OC2-tableCell OC2-tableMember" title="${_member.name} [${_member.id}]<br />Last OC joined: ${stringifyTimestampOld(timestampOldDiff(_member.lastCrime))}<br />${_memberInactiveTitle}">
<div class="OC2-tableLastCrime OC2-lazyCountup" data-countup="${timestampOldDiff(_member.lastCrime)}"><span>${_lastCrimeIcon}</span></div>
<a href="https://www.torn.com/profiles.php?XID=${_member.id}"><span class="OC2-memberName ${_memberNameColor}">${_member.name}</span><span class="screen-reader-hidden">: Last OC joined: ${stringifyTimestampOld(timestampOldDiff(_member.lastCrime))}: ${_memberInactiveTitle}</span></a>
</div>
<div class="OC2-tableCell OC2-tableStatus"><span class="screen-reader-hidden">Status:</span>${styleMemberStatus(_member.status,_member.statusDesc)}</div>
</li>`)
_afterElm.after(_outputHTML)
})
let _ignoreText = (``)
if (userSettings.memberIgnoreList.length > 0) {
_ignoreText = (`(${userSettings.memberIgnoreList.length} ignored)`)
}
$(".OC2-memberTableFooter").not(".OC2-settingsFooter").html(`<div class="OC2-memberTableFooterFlex"><div title="${_skipTitleText}">${availableMembers} members available ${_ignoreText}</div><div class="OC2-footerSoonAvailableMembers">+${soonAvailableMembers} available within 24h</div><div class="OC2-footerCrimeSlots">[calculating crime slots...]</div></div>`)
$(".OC2-memberTableFooter div.OC2-memberTableFooterFlex").css({
"display": "flex",
"flex-wrap": "wrap",
"flex-direction": "row",
"flex-flow": "space-evenly"
})
$(".OC2-memberTableFooter div div").css({
"padding": "2px 5px",
"flex-grow": 1,
})
}
function calculateCrimeSlots() {
//add crime slot count to table
let _availableCrimeSlotTitleText = "All crime slots are occupied by members"
if (availableCrimeSlotsCount > 0) {
_availableCrimeSlotTitleText = (`<span style="font-weight: bold">Available slots:</span><br />`)
for (const [_slotLevel,_slotCount] of Object.entries(availableCrimeSlots)) {
_availableCrimeSlotTitleText = _availableCrimeSlotTitleText + `Lvl ${_slotLevel} x ${_slotCount}<br />`
}
}
$(".OC2-footerCrimeSlots").attr('title', _availableCrimeSlotTitleText)
$(".OC2-footerCrimeSlots").html(`${availableCrimeSlotsCount} crime slots available`)
}
function calculateSoonAvailMembers() {
//add mouseover title for members nearing completion
let _soonAvailableMembersTitleText = ""
if (soonAvailableMembers > 0) {
_soonAvailableMembersTitleText = (`<span style="font-weight: bold">OCs finishing soon:</span><br />`)
let _soonAvailableMemberCRObj = {}
for (let n = 0; n < soonAvailableMembersList.length; n++) {
_soonAvailableMemberCRObj[memberInfo[soonAvailableMembersList[n]].crimeInfo.crimeDifficulty] = (_soonAvailableMemberCRObj[memberInfo[soonAvailableMembersList[n]].crimeInfo.crimeDifficulty] || 0) + 1
//_soonAvailableMembersTitleText = _soonAvailableMembersTitleText + (`Lvl ${memberInfo[soonAvailableMembersList[n]].crimeInfo.crimeDifficulty} (${memberInfo[soonAvailableMembersList[n]].crimeInfo.crimeSuccess}%): ${memberInfo[soonAvailableMembersList[n]].name}<br />`)
}
for (const [_slotLevel,_slotCount] of Object.entries(_soonAvailableMemberCRObj)) {
_soonAvailableMembersTitleText = _soonAvailableMembersTitleText + `Lvl ${_slotLevel} x ${_slotCount} members<br />`
}
}
$(".OC2-footerSoonAvailableMembers").attr('title', _soonAvailableMembersTitleText)
}
function putMemberInfoIntoTable() {
//fix for tornPDA, idk why but checking the li.tablecell works but checking the ul.table doesn't
if ($(".OC2-memberTable .OC2-tableCell")[0]) {
return
}
availMemberList = []
for (var _key of Object.keys(memberInfo)) {
if (memberInfo[_key].crimeInfo) {
continue //skip all members in crimes
}
availMemberList.push(memberInfo[_key])
}
sortAvailMembers()
putAvailMembersIntoTable(availMemberList, $(".OC2-memberTable li.OC2-titleLiAvailableMembers"))
sortCrimeInfo()
putCrimeInfoIntoTable(crimeListUninitiated, $(".OC2-memberTable li.OC2-titleLiUninitiated").eq(0))
putCrimeInfoIntoTable(crimeListRecruiting, $(".OC2-memberTable li.OC2-titleLiRecruiting").eq(0))
putCrimeInfoIntoTable(crimeListPlanning, $(".OC2-memberTable li.OC2-titleLiPlanning").eq(0))
$(".OC2-hideAtStart").css({
"display": ""
})
$("#OC2-titleUninitiated > span.toggleCatCrimesButton").on("click", (event) => {
if ((event.currentTarget.attributes.class.value).includes("OC2-crimeListExpand")) {
//expanded, shut all
$(event.currentTarget).removeClass("OC2-crimeListExpand")
$(event.currentTarget).html(crimeListShowText)
crimeIDListUninitiated.forEach(function(item) {
$("li.OC2-crimeLi.OC2-crimeID_"+item).removeClass("OC2-crimeLiActive")
$(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonShowText)
$(".OC2-crimeID_"+item+" .hideMembersButton").removeClass("text-hide")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).removeClass("crimeMemberLiShow")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideUp()
})
} else {
//shut, expand all
$(event.currentTarget).addClass("OC2-crimeListExpand")
$(event.currentTarget).html(crimeListHideText)
crimeIDListUninitiated.forEach(function(item) {
$("li.OC2-crimeLi.OC2-crimeID_"+item).addClass("OC2-crimeLiActive")
$(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonHideText)
$(".OC2-crimeID_"+item+" .hideMembersButton").addClass("text-hide")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).addClass("crimeMemberLiShow")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideDown()
})
}
styleCrimeLiActive()
})
$("#OC2-titleRecruiting > span.toggleCatCrimesButton").on("click", (event) => {
if ((event.currentTarget.attributes.class.value).includes("OC2-crimeListExpand")) {
//expanded, shut all
$(event.currentTarget).removeClass("OC2-crimeListExpand")
$(event.currentTarget).html(crimeListShowText)
crimeIDListRecruiting.forEach(function(item) {
$("li.OC2-crimeLi.OC2-crimeID_"+item).removeClass("OC2-crimeLiActive")
$(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonShowText)
$(".OC2-crimeID_"+item+" .hideMembersButton").removeClass("text-hide")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).removeClass("crimeMemberLiShow")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideUp()
})
} else {
//shut, expand all
$(event.currentTarget).addClass("OC2-crimeListExpand")
$(event.currentTarget).html(crimeListHideText)
crimeIDListRecruiting.forEach(function(item) {
$("li.OC2-crimeLi.OC2-crimeID_"+item).addClass("OC2-crimeLiActive")
$(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonHideText)
$(".OC2-crimeID_"+item+" .hideMembersButton").addClass("text-hide")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).addClass("crimeMemberLiShow")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideDown()
})
}
styleCrimeLiActive()
})
$("#OC2-titleFull > span.toggleCatCrimesButton").on("click", (event) => {
if ((event.currentTarget.attributes.class.value).includes("OC2-crimeListExpand")) {
//expanded, shut all
$(event.currentTarget).removeClass("OC2-crimeListExpand")
$(event.currentTarget).html(crimeListShowText)
crimeIDListFull.forEach(function(item) {
$("li.OC2-crimeLi.OC2-crimeID_"+item).removeClass("OC2-crimeLiActive")
$(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonShowText)
$(".OC2-crimeID_"+item+" .hideMembersButton").removeClass("text-hide")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).removeClass("crimeMemberLiShow")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideUp()
})
} else {
//shut, expand all
$(event.currentTarget).addClass("OC2-crimeListExpand")
$(event.currentTarget).html(crimeListHideText)
crimeIDListFull.forEach(function(item) {
$("li.OC2-crimeLi.OC2-crimeID_"+item).addClass("OC2-crimeLiActive")
$(".OC2-crimeID_"+item+" .hideMembersButton").html(membersButtonHideText)
$(".OC2-crimeID_"+item+" .hideMembersButton").addClass("text-hide")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).addClass("crimeMemberLiShow")
$(".OC2-crimeMemberLi.OC2-crimeID_"+item).slideDown()
})
}
styleCrimeLiActive()
})
convertItemIDArrayToItems()
styleTable()
arrangeMemberDirectionality()
calculateCrimeSlots()
calculateSoonAvailMembers()
checkDefaultHideState()
if (OC2_timerID) {
clearTimeout(OC2_timerID)
}
timerTick()
}
async function putCrimeInfoIntoTable(_crimeArray, _afterElm) {
let _countdownText = ""
let _countdownToTimestamp = ""
let _countdownMouseover = ""
let _memberOutputHTML = ""
let _userIndicatorClass = ""
let _userIndicatorCrimeClass = ""
let _crimeListType = ""
if (_crimeArray.length > 0) {
for (let i = 0; i < _crimeArray.length; i++) {
//crimes in recruiting include both crimes with members (has planning_at) and with no members (don't have planning_at)
if (_crimeArray[i].status == "Recruiting") {
//get crimes with no members
if (_crimeArray[i].planning_at == null) {
_countdownText = ((_isWindowSmall.matches) || (_isWindowTiny.matches)) ? "E:" : "Expires: "
_countdownToTimestamp = _crimeArray[i].expired_at
_countdownMouseover = "Time until this crime is no longer be available"
_crimeListType = "OC2-crimeUninitiated"
crimeIDListUninitiated.push(_crimeArray[i].id)
} else {
_countdownText = ((_isWindowSmall.matches) || (_isWindowTiny.matches)) ? "J:" : "Join in: "
_countdownToTimestamp = _crimeArray[i].ready_at
_countdownMouseover = "Time until this crime needs a new member to join to continue planning"
_crimeListType = "OC2-crimeRecruiting"
crimeIDListRecruiting.push(_crimeArray[i].id)
if ((_countdownToTimestamp < Math.floor(Date.now()/1000)) && (userSettings?.showNegativeTimes == "negative-timer-show")) {
_countdownText = ((_isWindowSmall.matches) || (_isWindowTiny.matches)) ? "D:" : "Delay:"
_countdownMouseover = "Delay in planning due to lack of members joining"
}
}
}
//crimes filled with members move to status = planning
if (_crimeArray[i].status == "Planning") {
_countdownText = ((_isWindowSmall.matches) || (_isWindowTiny.matches)) ? "R:" : "Ready in: "
_countdownToTimestamp = _crimeArray[i].ready_at
_countdownMouseover = "Time until this crime is ready to start"
_crimeListType = "OC2-crimeFull"
crimeIDListFull.push(_crimeArray[i].id)
if ((_countdownToTimestamp < Math.floor(Date.now()/1000)) && (userSettings?.showNegativeTimes == "negative-timer-show")) {
_countdownText = ((_isWindowSmall.matches) || (_isWindowTiny.matches)) ? "D:" :"Delay: "
_countdownMouseover = "Delay since crime should have started due to members not being available"
}
}
_memberOutputHTML = ""
_userIndicatorCrimeClass = ""
let _memberCount = 0
let _crimeWarningIcon = ""
let _crimeWarningMouseover = ""
let _warningSuccessChance = ""
let _warningItemNeeded = ""
let _crimeSlotPositionArray = []
let _crimeSuccessRateList = []
//count number of slots filled
for (let j = 0; j < (_crimeArray[i].slots).length; j++) {
let _crimeSlotMemberName = `<span class="OC2-textGray"> N/A</span>`
let _crimeSlotMemberID = ""
let _crimeSlotMemberStatus = ""
let _crimeSlotPosition = ""
let _crimeItem = ""
let _crimeItemIconDisplay = crimeItemIcon
let _crimeItemMouseover = ""
let _crimeSuccess = ""
let _crimeSuccessWrapper = ""
let _crimeProgressString = ""
_userIndicatorClass = ""
//collect all success rates into an array if the crime status is planning
if (_crimeArray[i].status == "Planning") {
_crimeSuccessRateList.push(_crimeArray[i].slots[j].checkpoint_pass_rate)
}
_crimeSlotPositionArray.push(_crimeArray[i].slots[j].position)
if (_crimeArray[i].slots[j].item_requirement) {
if(!(_crimeArray[i].slots[j].item_requirement.id in itemIDObj)) {
itemIDObj[_crimeArray[i].slots[j].item_requirement.id] = {
"name": ""
};
}
_crimeItemMouseover = (`Required item: <#${_crimeArray[i].slots[j].item_requirement.id}>`)
if (_crimeArray[i].slots[j].item_requirement.is_reusable) {
_crimeItemMouseover += (` (reusable)`)
_crimeItemIconDisplay += (`<span style="vertical-align:top">∞</span>`)
}
if (_crimeArray[i].slots[j].item_requirement.is_available) {
_crimeItemMouseover += (`<br />Item is owned by member`)
}
_crimeItem = (`<span class="OC2-itemHave${_crimeArray[i].slots[j].item_requirement.is_available}">${_crimeItemIconDisplay}</span>`)
}
if (_crimeArray[i].slots[j].user) {
if (_crimeArray[i].slots[j].user.id == userInfo.id) {
_userIndicatorClass = "OC2-userIndicator"
_userIndicatorCrimeClass = "OC2-userIndicator"
}
_memberCount = _memberCount + 1
if (userSettings?.showProgressPercentage == "progress-show") {
_crimeProgressString = (`<span style="font-size: 9px">${memberInfo[_crimeArray[i].slots[j].user.id].crimeInfo.crimeProgress}%</span>`)
}
_crimeSlotMemberName = `<a href="https://www.torn.com/profiles.php?XID=${_crimeArray[i].slots[j].user.id}">${memberInfo[_crimeArray[i].slots[j].user.id].name}</a> ${_crimeProgressString} `
_crimeSlotMemberStatus = styleMemberStatus(memberInfo[_crimeArray[i].slots[j].user.id].status, memberInfo[_crimeArray[i].slots[j].user.id].statusDesc)
if (_crimeArray[i].slots[j].checkpoint_pass_rate > 75) {
_crimeSuccessWrapper = "OC2-highSuccess"
} else if (_crimeArray[i].slots[j].checkpoint_pass_rate > 50) {
_crimeSuccessWrapper = "OC2-midSuccess"
} else {
_crimeSuccessWrapper = "OC2-lowSuccess"
}
if (_crimeArray[i].slots[j].checkpoint_pass_rate < ((userSettings?.warnLowSuccessPercentage) ? userSettings.warnLowSuccessPercentage : 50)) {
_crimeSuccessWrapper = "OC2-lowSuccess"
}
_crimeSuccess = (`<span class="${_crimeSuccessWrapper}">${_crimeArray[i].slots[j].checkpoint_pass_rate}</span>`)
if (_crimeArray[i].slots[j].checkpoint_pass_rate < userSettings?.warnLowSuccessPercentage) {
if (_warningSuccessChance.length < 1) {
_warningSuccessChance = (`Low Success Chance:<br />`)
} else {
_warningSuccessChance += "<br />"
}
_warningSuccessChance += ` ${_crimeArray[i].slots[j].checkpoint_pass_rate}% - ${memberInfo[_crimeArray[i].slots[j].user.id].name}`
}
if (_crimeArray[i].slots[j].item_requirement?.is_available == false) { //item is not available for member
if (_warningItemNeeded.length < 1) {
_warningItemNeeded = (`Item Missing:<br />`)
} else {
_warningItemNeeded += "<br />"
}
_warningItemNeeded += ` <#${_crimeArray[i].slots[j].item_requirement.id}> - ${memberInfo[_crimeArray[i].slots[j].user.id].name}<br />`
}
} else {
availableCrimeSlots[_crimeArray[i].difficulty] = (availableCrimeSlots[_crimeArray[i].difficulty] || 0) + 1
availableCrimeSlotsCount += 1
if (userSettings.showSelfSuccessRate == "self-success-hide") {
_crimeSuccess = (`<span class="OC2-textGray">-</span>`)
} else {
_crimeSuccess = (`<span class="OC2-textGray">${_crimeArray[i].slots[j].checkpoint_pass_rate}</span>`)
}
}
_memberOutputHTML += (`<li class="table-cell OC2-crimeMemberLi OC2-crimeID_${_crimeArray[i].id} ${_userIndicatorClass}">
<div class="OC2-tableCell OC2-tableCrimeMemberSuccess">${_crimeSuccess}</div>
<div class="OC2-tableCell OC2-tableCrimeMemberItem" title="${_crimeItemMouseover}" >${_crimeItem}</div>
<div class="OC2-tableCell OC2-hideSmall OC2-tableCrimePosition">${_crimeArray[i].slots[j].position}</div>
<div class="OC2-tableCell OC2-tableCrimeMemberName">${_crimeSlotMemberName}</div>
<div class="OC2-tableCell OC2-tableCrimeMemberStatus">${_crimeSlotMemberStatus}</div>
</li>`)
}
_crimeWarningMouseover = _warningSuccessChance + (((_warningItemNeeded.length > 0) && (_warningSuccessChance.length > 0)) ? "<br />": "") + _warningItemNeeded
if (_warningSuccessChance.length > 0) {
_crimeWarningIcon += "%"
}
if (_warningItemNeeded.length > 0) {
_crimeWarningIcon += crimeItemIcon
}
if (_crimeWarningIcon.length > 0) {
_crimeWarningIcon = "[" + _crimeWarningIcon + "] "
}
let _outputHTML = (`<li class="table-cell OC2-crimeLi ${_crimeListType} OC2-crimeID_${_crimeArray[i].id} ${_userIndicatorCrimeClass}">
<div class="OC2-tableCell OC2-tableCrimeMemberCount OC2-crimeID_${_crimeArray[i].id}"><span class="hideMembersButton">${membersButtonShowText}</span> ${_memberCount} / ${(_crimeArray[i].slots).length}</div>
<div class="OC2-tableCell OC2-tableCrime"><span class="OC2-crimeMouseoverWarning" title="${_crimeWarningMouseover}">${_crimeWarningIcon}</span><a href="https://www.torn.com/factions.php?step=your&type=12#/tab=crimes&crimeId=${_crimeArray[i].id}">Lv${_crimeArray[i].difficulty} ${_crimeArray[i].name}</a></div>
<div class="OC2-tableCell OC2-tableCountdown OC2-crimeID_${_crimeArray[i].id}" title="${_countdownMouseover}"><span class="OC2-countdownText">${_countdownText}</span> <span class="OC2-countdown" data-countdown="${_countdownToTimestamp}">${_countdownToTimestamp}</span></div>
</li>`)
_afterElm.after(_outputHTML)
$("li.OC2-crimeID_"+_crimeArray[i].id).after(_memberOutputHTML)
$("div.OC2-crimeID_"+_crimeArray[i].id).off().on("click", (event) => {
toggleMemberView(((event.currentTarget.attributes.class.value).split("OC2-crimeID_"))[1])
if ($(event.currentTarget).parent(".OC2-crimeLi").hasClass("OC2-crimeLiActive")) {
$(event.currentTarget).parent(".OC2-crimeLi.OC2-crimeLiActive").removeClass("OC2-crimeLiActive")
} else {
$(event.currentTarget).parent(".OC2-crimeLi").not(".OC2-crimeLiActive").addClass("OC2-crimeLiActive")
}
styleCrimeLiActive()
}).css({"cursor": "pointer"})
//give numbers to crimes with more than one of the same named role
let _crimeSlotPositionMap = _crimeSlotPositionArray.reduce((cnt, cur) => (cnt[cur] = cnt[cur] + 1 || 1, cnt), {})
for (const [_pos, _count] of Object.entries(_crimeSlotPositionMap)) {
if (_count > 1) {
let _matchedDivs = $("li.OC2-crimeID_"+_crimeArray[i].id+" .OC2-tableCrimePosition:contains("+_pos+")")
for (let m = 0; m < _matchedDivs.length; m++) {
_matchedDivs[m].innerHTML = _matchedDivs[m].innerHTML + ` ${m+1}`
}
}
}
//logic for Allenone's API can go here-------------------------------------------------
if (userSettings.showAllenoneScript == "torn-probability-hide") {
//do nothing
} else {
if (_crimeArray[i].status == "Planning") {
if (!calculatedSuccessChanceObj[_crimeArray[i].id]) {
if (tornProbabilityAvailableScenarios.includes(_crimeArray[i].name)) {
getTornProbabilityCrimeSuccess(_crimeArray[i].name, _crimeSuccessRateList, _crimeArray[i].id)
}
} else {
let _crimeLi_originalHTML = $("li.OC2-crimeLi.OC2-crimeID_" + _crimeArray[i].id + " div.OC2-tableCrime").html()
$("li.OC2-crimeLi.OC2-crimeID_" + _crimeArray[i].id + " div.OC2-tableCrime").html(_crimeLi_originalHTML + ` ${calculatedSuccessChanceObj[_crimeArray[i].id]}%`)
}
}
}
}
} else {
_afterElm.after(`<li class="table-cell OC2-crimeLi"><div class="OC2-tableCell OC2-tableCrime">None</div></li>`)
}
}
//templating functions
async function generateInsertHTML() {
if ($(".OC2-memberViewer .OC2-memberTable")[0]) {
return
}
let _insertHTML = (`
<div class="category-wrap OC2-memberViewer m-top10">
<div class="title-black top-round t-overflow">OC 2.0 Overview <a href="https://www.torn.com/OC2_Settings_Page" target="_blank"><span title="Go to Settings Page" class="extraSettingsButton">⚙</span></a><span class="hideLazyMembersButton OC2-hideAtStart"></span><span class="hideCrimesButton OC2-hideAtStart"></span></div>
<div class="cont-gray OC2-memberTableErrorDisplay" style="display:none; padding: 5px 0"></div>
<div class="cont-gray OC2-memberTable" style="display:none"><ul class="table-body">
<li class="table-cell OC2-availableMembers OC2-titleLiAvailableMembers"><div class="OC2-titleCell OC2-fancyBg">Members not in an OC<div class="OC2-sortTypeMember">Sort: <div id="sortOCButton">OC</div><div id="sortActiveButton">active</div></div></div></li>
<li class="table-cell OC2-crimeLi OC2-titleLiCrimeSeciton"><div class="OC2-titleCell OC2-fancyBg">Crimes<div class="OC2-sortType">Sort: <div id="sortLevelButton">level</div><div id="sortTimeButton">time</div></div></div></li>
<li class="table-cell OC2-crimeLi OC2-titleLiUninitiated"><div class="OC2-titleCell" id="OC2-titleUninitiated"><span class="catCrimeTitle">Uninitiated Crimes</span> <span class="toggleCatCrimesButton">${crimeListShowText}<span></div></li>
<li class="table-cell OC2-crimeLi OC2-horizLine"></li>
<li class="table-cell OC2-crimeLi OC2-titleLiRecruiting"><div class="OC2-titleCell" id="OC2-titleRecruiting"><span class="catCrimeTitle">Recruiting Crimes</span> <span class="toggleCatCrimesButton">${crimeListShowText}<span></div></li>
<li class="table-cell OC2-crimeLi OC2-horizLine"></li>
<li class="table-cell OC2-crimeLi OC2-titleLiPlanning"><div class="OC2-titleCell" id="OC2-titleFull"><span class="catCrimeTitle">Full Crimes</span> <span class="toggleCatCrimesButton">${crimeListShowText}<span></div></li>
</ul></div>
<div class="OC2-memberTableFooter"></div>
</div>`)
//what to do in normal crimes2.0 page
$(".OC2-hideAtStart").css({
"display": "none"
})
if (checkCrimesPage()) {
$("div#faction-crimes").before(_insertHTML)
checkDefaultSortState()
styleTable()
$(".hideCrimesButton").off().on("click", event => {
toggleCrimeView()
})
$(".OC2-sortType div").off().on("click", event => {
resortCrimeTable(event.currentTarget)
})
$(".OC2-sortTypeMember div").off().on("click", event => {
resortAvailMemberTable(event.currentTarget)
})
$(".hideLazyMembersButton").off().on("click", event => {
toggleLazyMembersView()
})
} else //this "else" is important to like the two if statements. Without it, it won't load on the faction -> crimes OC page because the second if statement takes precidence due to await
//what to do if traveling AND on faction page
if (await checkTravelFactionPage()) {
waitForElm('div#react-root').then((elm) => {
$(elm).before(_insertHTML)
checkDefaultSortState()
styleTable()
$(".hideCrimesButton").off().on("click", event => {
toggleCrimeView()
})
$(".OC2-sortType div").off().on("click", event => {
resortCrimeTable(event.currentTarget)
})
$(".OC2-sortTypeMember div").off().on("click", event => {
resortAvailMemberTable(event.currentTarget)
})
$(".hideLazyMembersButton").off().on("click", event => {
toggleLazyMembersView()
})
if (myAPIData) {
putMemberInfoIntoTable()
}
})
}
}
function checkDefaultHideState() {
$(".OC2-memberTable").show()
if (userSettings.memberShow == "member-hide") {
$(".hideLazyMembersButton").html(lazyMembersButtonShowText)
$(".OC2-availableMembers").hide()
$(".OC2-memberAvailable").hide()
} else {
$(".hideLazyMembersButton").addClass("text-hide")
$(".hideLazyMembersButton").html(lazyMembersButtonHideText)
}
if (userSettings.crimesShow == "crimes-hide") {
$(".hideCrimesButton").html(crimeButtonShowText)
$(".OC2-crimeMemberLi").hide()
$(".OC2-crimeLi").hide()
} else {
$(".hideCrimesButton").addClass("text-hide")
$(".hideCrimesButton").html(crimeButtonHideText)
$(".OC2-crimeMemberLi").hide();
}
}
function checkDefaultSortState() {
//userSettings.memberSort "OC-desc", //OC-asc / OC-desc / active-asc / active-desc
//userSettings.sortType "time-asc", //time-asc / time-desc / level-asc / level-desc
let _memberTarget = userSettings.memberSort.split("-")[0]
let _memberDirection = userSettings.memberSort.split("-")[1]
let _crimeTarget = userSettings.sortType.split("-")[0]
let _crimeDirection = userSettings.sortType.split("-")[1]
_memberTarget = _memberTarget[0].toUpperCase() + _memberTarget.slice(1)
_crimeTarget = _crimeTarget[0].toUpperCase() + _crimeTarget.slice(1)
let _arrowDisplayMember = settingsButtonAscText
let _arrowDisplayCrime = settingsButtonAscText
if (_memberDirection == "desc") {
_arrowDisplayMember = settingsButtonDescText
}
if (_crimeDirection == "desc") {
_arrowDisplayCrime= settingsButtonDescText
}
$(`.OC2-sortTypeMember div[id=sort${_memberTarget}Button]`).addClass("text-underline")
$(`.OC2-sortTypeMember div[id=sort${_memberTarget}Button]`).html(`${_memberTarget.toLowerCase()}${_arrowDisplayMember}`)
$(`.OC2-sortType div[id=sort${_crimeTarget}Button]`).addClass("text-underline")
$(`.OC2-sortType div[id=sort${_crimeTarget}Button]`).html(`${_crimeTarget.toLowerCase()}${_arrowDisplayCrime}`)
}
function insertOCNotifier() {
let _userNotice = (`<a href="https://www.torn.com/factions.php?step=your#/tab=crimes"><span class="OC2-redtext">No active OC.</span></a>`)
let _userMouseover = (`You are not currently participating in an OC.`)
if (userInfo.crimeInfo) {
_userNotice = (`<span class="OC2-normaltext"><a href="https://www.torn.com/factions.php?step=your&type=5#/tab=crimes&crimeId=${userInfo.crimeInfo.crimeId}">Lv ${userInfo.crimeInfo.crimeDifficulty} ${userInfo.crimeInfo.crimeName}</a></span>`)
_userMouseover = (`${userInfo.crimeInfo.crimePosition} (${userInfo.crimeInfo.crimeSuccess}%)`)
}
let _insertHTML = (`<div class="OC2-sidebarNotice" title="${_userMouseover}"><a href="https://www.torn.com/factions.php?step=your#/tab=crimes"><span style="font-weight: bold">OC 2.0:</span></a>
${_userNotice}
</div>`)
$('div[class^="sidebar_"] div[class^="user-information_"] div[class^="toggle-block_"] div[class^="toggle-content_"] div[class^="content_"]').append(_insertHTML)
styleOCNotifier()
}
//on click functions
function toggleCrimeView() {
if ($(".hideCrimesButton").hasClass("text-hide")) {
$(".hideCrimesButton").html(crimeButtonShowText)
$(".hideCrimesButton").removeClass("text-hide")
$(".OC2-crimeLi").slideUp()
} else {
$(".hideCrimesButton").html(crimeButtonHideText)
$(".hideCrimesButton").addClass("text-hide")
$(".OC2-crimeLi").slideDown()
}
$(".hideMembersButton").html(membersButtonShowText)
$(".hideMembersButton").removeClass("text-hide")
$(".OC2-crimeMemberLi").hide()
$(".OC2-crimeLi.OC2-crimeLiActive").removeClass("OC2-crimeLiActive")
styleCrimeLiActive()
}
function toggleMemberView(_crimeID) {
if ($(".OC2-crimeID_"+_crimeID+" .hideMembersButton").hasClass("text-hide")) {
$(".OC2-crimeID_"+_crimeID+" .hideMembersButton").html(membersButtonShowText)
$(".OC2-crimeID_"+_crimeID+" .hideMembersButton").removeClass("text-hide")
$(".OC2-crimeMemberLi.OC2-crimeID_"+_crimeID).removeClass("crimeMemberLiShow")
$(".OC2-crimeMemberLi.OC2-crimeID_"+_crimeID).slideUp()
} else {
$(".OC2-crimeID_"+_crimeID+" .hideMembersButton").html(membersButtonHideText)
$(".OC2-crimeID_"+_crimeID+" .hideMembersButton").addClass("text-hide")
$(".OC2-crimeMemberLi.OC2-crimeID_"+_crimeID).addClass("crimeMemberLiShow")
$(".OC2-crimeMemberLi.OC2-crimeID_"+_crimeID).slideDown()
}
}
//OC2-availableMembers
function toggleLazyMembersView() {
if ($(".hideLazyMembersButton").hasClass("text-hide")) {
$(".hideLazyMembersButton").html(lazyMembersButtonShowText)
$(".hideLazyMembersButton").removeClass("text-hide")
$(".OC2-availableMembers").slideUp()
$(".OC2-memberAvailable").slideUp()
} else {
$(".hideLazyMembersButton").html(lazyMembersButtonHideText)
$(".hideLazyMembersButton").addClass("text-hide")
$(".OC2-availableMembers").slideDown()
$(".OC2-memberAvailable").slideDown()
}
}
function resortCrimeTable(_target) {
$(".OC2-sortType div").removeClass("text-underline")
$(".OC2-sortType div#sortLevelButton").html("level")
$(".OC2-sortType div#sortTimeButton").html("time")
$(_target).addClass("text-underline")
if ($(_target).attr("id") == "sortLevelButton") {
if (userSettings.sortType == "level-asc") {
userSettings.sortType = "level-desc"
$(_target).html(`level${settingsButtonDescText}`)
} else {
userSettings.sortType = "level-asc"
$(_target).html(`level${settingsButtonAscText}`)
}
}
if ($(_target).attr("id") == "sortTimeButton") {
if (userSettings.sortType == "time-asc") {
userSettings.sortType = "time-desc"
$(_target).html(`time${settingsButtonDescText}`)
} else {
userSettings.sortType = "time-asc"
$(_target).html(`time${settingsButtonAscText}`)
}
}
sortCrimeInfo()
putCrimeInfoIntoTable(crimeListUninitiated, $(".OC2-memberTable li.OC2-titleLiUninitiated").eq(0))
putCrimeInfoIntoTable(crimeListRecruiting, $(".OC2-memberTable li.OC2-titleLiRecruiting").eq(0))
putCrimeInfoIntoTable(crimeListPlanning, $(".OC2-memberTable li.OC2-titleLiPlanning").eq(0))
convertItemIDArrayToItems()
styleTable()
timerTick()
$("li.OC2-crimeLi .hideMembersButton").removeClass("text-hide")
$("li.OC2-crimeMemberLi").hide()
if ( !$(".hideLazyMembersButton").eq(0).hasClass("text-hide") ) {
$("li.OC2-availableMembers").hide()
$("li.OC2-memberAvailable").hide()
}
}
function resortAvailMemberTable(_target) {
$(".OC2-sortTypeMember div").removeClass("text-underline")
$(".OC2-sortTypeMember div#sortOCButton").html("OC")
$(".OC2-sortTypeMember div#sortActiveButton").html("active")
$(_target).addClass("text-underline")
if ($(_target).attr("id") == "sortOCButton") {
if (userSettings.memberSort == "OC-asc") {
userSettings.memberSort = "OC-desc"
$(_target).html(`OC${settingsButtonAscText}`)
} else {
userSettings.memberSort = "OC-asc"
$(_target).html(`OC${settingsButtonDescText}`)
}
}
if ($(_target).attr("id") == "sortActiveButton") {
if (userSettings.memberSort == "active-desc") {
userSettings.memberSort = "active-asc"
$(_target).html(`active${settingsButtonAscText}`)
} else {
userSettings.memberSort = "active-desc"
$(_target).html(`active${settingsButtonDescText}`)
}
}
sortAvailMembers()
putAvailMembersIntoTable(availMemberList, $(".OC2-memberTable li.OC2-titleLiAvailableMembers"))
styleTable()
arrangeMemberDirectionality()
calculateCrimeSlots()
calculateSoonAvailMembers()
$("li.OC2-crimeLi .hideMembersButton").removeClass("text-hide")
$("li.OC2-crimeLi.OC2-crimeLiActive").removeClass("OC2-crimeLiActive")
styleCrimeLiActive()
$("li.OC2-crimeMemberLi").hide()
if ( !$(".hideCrimesButton").eq(0).hasClass("text-hide") ) {
$("li.OC2-crimeLi").hide()
}
}
//prettifying functions
function styleCrimeLiActive() {
$(".OC2-crimeLi.OC2-crimeLiActive").not(".OC2-userIndicator").css({
"background-color": "rgba(150,150,150,0.2)"
})
$(".OC2-crimeLi").not(".OC2-crimeLiActive").not(".OC2-userIndicator").css({
"background-color": "transparent"
})
if ($("#dark-mode-state").prop("checked")) {
$(".OC2-crimeLi.OC2-crimeLiActive").not(".OC2-userIndicator").css({
"background-color": "rgba(0,0,0,0.3)"
})
}
}
function styleMemberStatus(_statusState, _statusDesc) {
return (`<span title="${_statusDesc}" class="OC2-statusText ${_statusState.toLowerCase()}">${_statusState}</span><span title="${_statusDesc}" class="OC2-statusText OC2-hideSmall ${_statusState.toLowerCase()}">${_statusDesc}</span>`)
}
function styleTable() {
/* notes to self
* Small: 386px
* Tiny: 320px
* Normal: 784px
*/
$(".OC2-memberTable ul.table-body").css({
"display": "flex",
"flex-direction": "row",
"flex-wrap": "wrap"
})
$(".OC2-memberTable li.table-cell").not(".OC2-memberAvailable").css({
"order": 420
})
$(".OC2-titleLiAvailableMembers").css({
"order": 0
})
$(".OC2-memberTable a").css({
"color": colorObj.link[colorDisplayMode],
"text-decoration": "none"
})
$(".extraSettingsButton").css({
"color": colorObj.dark_bg_link[colorDisplayMode],
"text-decoration": "none"
})
$(".extraSettingsButton").on("mouseenter", function(event) {
$(event.currentTarget).css({
"color": colorObj.recolor.yellow[colorDisplayMode]
})
})
$(".extraSettingsButton").on("mouseleave", function(event) {
$(event.currentTarget).css({
"color": colorObj.dark_bg_link[colorDisplayMode]
})
})
$(".OC2-memberTable a").hover(
function() {
$(this).css({
"text-decoration": "underline"
})
},
function() {
$(this).css({
"text-decoration": "none"
})
})
$(".OC2-memberTable div.OC2-tableCrimeMemberName a").css({
"color": "inherit",
})
$(".OC2-memberTable div.OC2-tableMember a").css({
"color": "inherit",
})
$(".OC2-memberTable li.table-cell").css({
"display": "flex",
"flex-direction": "row",
})
$(".OC2-memberTable li.OC2-memberAvailable").css({
"font-weight": "normal",
})
$(".OC2-memberTable .OC2-textGray").css({
"color": "rgb(153, 153, 153)",
})
$(".OC2-memberTable div").css({
"font-size": "11px",
"line-height": "12px",
"padding": "5px 0"
})
$(".OC2-memberTable div.OC2-titleCell").css({
"display": "inline",
"font-family": "Fjalla One",
"padding-left": "10px",
"width": containerMaxWidth,
"box-sizing": "border-box",
})
$(".OC2-memberTable div.OC2-titleCell.OC2-fancyBg").css({
"background": "repeating-linear-gradient(90deg, #2e2e2e, #2e2e2e 2px, #282828 0, #282828 4px)",
"color": colorObj.fancyBg[colorDisplayMode]
})
$(".OC2-memberTable div.OC2-tableLastCrime").css({
"width": "13px",
"display": "inline-block",
"margin": "auto",
"padding": "0 3px"
})
$(".OC2-memberTable div.OC2-tableLastCrime svg").css({
"display": "inline-block",
"margin": "0 auto -1px auto"
})
$(".OC2-memberTable div.OC2-tableMember").css({
"width": "148px",
})
$(".OC2-memberTable div.OC2-tableStatus").css({
"width": "222px",
"text-align": "left"
})
$(".OC2-statusText.okay").css({
"color": colorObj.recolor.green[colorDisplayMode]
})
$(".OC2-statusText.abroad, .OC2-statusText.traveling").css({
"color": colorObj.recolor.blue[colorDisplayMode]
})
$(".OC2-statusText.hospital").css({
"color": colorObj.recolor.red[colorDisplayMode]
})
$(".OC2-statusText.jail").css({
"color": colorObj.recolor.red[colorDisplayMode]
})
$(".OC2-statusText.federal").css({
"color": colorObj.recolor.red[colorDisplayMode]
})
$(".OC2-memberName.federal").css({
"text-decoration": "line-through",
"color": colorObj.recolor.red[colorDisplayMode]
})
$(".OC2-memberName.inactive").css({
"color": colorObj.recolor.red[colorDisplayMode]
})
$(".OC2-memberTable div.OC2-tableStatus img").css({
"height": "11px"
})
$(".OC2-memberTable div.OC2-tableCrimeMemberCount").css({
"width": "50px",
})
$(".OC2-memberTable div.OC2-tableCrimePosition").css({
"width": "100px",
"font-size": "10px",
})
$(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
"width": "230px",
})
$(".OC2-memberTable div.OC2-tableCrimeMemberStatus").css({
"width": "auto",
})
$(".OC2-memberTable div.OC2-tableCrimeMemberSuccess").css({
"width": "25px",
"text-align": "center"
})
$(".OC2-memberTable div.OC2-tableCrimeMemberItem").css({
"width": "40px",
"text-align": "center"
})
$(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-highSuccess").css({
"color": colorObj.recolor.green[colorDisplayMode]
})
$(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-midSuccess").css({
"color": colorObj.recolor.yellow[colorDisplayMode]
})
$(".OC2-memberTable .OC2-tableCrimeMemberSuccess .OC2-lowSuccess").css({
"color": colorObj.recolor.red[colorDisplayMode]
})
$(".OC2-memberTable .OC2-itemHavetrue").css({
"color": colorObj.recolor.green[colorDisplayMode]
})
$(".OC2-memberTable .OC2-itemHavefalse").css({
"color": colorObj.recolor.red[colorDisplayMode]
})
$(".OC2-memberTable div.OC2-tableCrime").css({
"width": "322px",
})
$(".OC2-crimeMouseoverWarning").css({
"margin-left": "10px",
"display": "inline-block",
"color": colorObj.recolor.red[colorDisplayMode]
})
$(".OC2-memberTable div.OC2-tableCountdown").css({
"width": "auto"
})
$(".OC2-memberTableFooter").css({
"border-radius": "0 0 5px 5px",
"background-color": colorObj.footerbg[colorDisplayMode],
"padding": "5px 5px 5px 10px",
"text-align": "center"
})
$(".hideCrimesButton").css({
"position": "absolute",
"right": "10px",
"cursor": "pointer"
})
$(".hideLazyMembersButton").css({
"position": "absolute",
"right": "100px",
"cursor": "pointer"
})
$(".OC2-memberTable li.OC2-memberAvailable").css({
"padding-left": "10px"
})
$(".OC2-memberTable li.OC2-crimeMemberLi").css({
"padding-left": "25px",
"background-color": colorObj.crimeselectbg[colorDisplayMode],
"width": containerMaxWidth
})
$(".OC2-memberTable li.OC2-crimeLi").not(".OC2-memberTable li[class*='OC2-titleLi']").css({
"padding-left": "20px",
"width": containerMaxWidth
})
$(".OC2-crimeLi").prev(".OC2-crimeMemberLi").css({
"border-radius": "0 0 15px 15px"
})
$(".OC2-sortType").css({
"display": "inline-block",
"padding-left": parseInt(containerMaxWidth) - 158 + "px",
})
$(".OC2-sortType div").css({
"display": "inline-block",
"font-family": "Arial",
"padding": "0 0 0 5px",
"width": "35px"
})
$(".OC2-memberTable .OC2-sortType div").css({
"text-decoration": "none"
})
$(".OC2-memberTable .OC2-sortType div.text-underline").css({
"text-decoration": "underline"
})
$(".OC2-sortTypeMember").css({
"display": "inline",
"padding-left": parseInt(containerMaxWidth) - 220 + "px",
})
$(".OC2-sortTypeMember div").css({
"display": "inline-block",
"font-family": "Arial",
"padding": "0 0 0 5px",
"width": "35px"
})
$(".OC2-memberTable .OC2-sortTypeMember div").css({
"text-decoration": "none"
})
$(".OC2-memberTable .OC2-sortTypeMember div.text-underline").css({
"text-decoration": "underline"
})
$(".OC2-userIndicator").css({
"background-color": colorObj.userindicatorbg[colorDisplayMode]
})
if (_isWindowTiny.matches) {
styleTableTinyScreen()
} else if (_isWindowSmall.matches) {
styleTableSmallScreen()
} else {
styleTableBigScreen()
}
$(".OC2-horizLine").css({
"border-bottom": "1px solid rgb(34,34,34)",
"width": $(this).parent().width() + "px",
"box-sizing": "border-box",
"height": "3px",
})
if (($(".OC2-crimeMemberLi").last().next()).length < 1) {
$(".OC2-crimeMemberLi").last().css({
"border-radius": "0 0 15px 15px",
})
}
$(".catCrimeTitle").css({
"width": "120px",
"display": "inline-block"
})
$(".toggleCatCrimesButton").css({
"font-size": "11px",
"font-family": "Arial",
"width": "50px",
//"margin": "0 0 0 50px",
"cursor": "pointer",
})
//hide screen reader fields
$(".screen-reader-hidden").css({
"position": "absolute",
"overflow": "hidden",
"width": "1px",
"height": "1px",
"clip": "rect(1px, 1px, 1px, 1px)",
"padding": "0",
"border": "0",
"white-space": "nowrap"
})
}
function styleTableSmallScreen() {
$(".OC2-memberTable li.OC2-memberAvailable").css({
"padding-left": "15px"
})
$(".OC2-hideSmall").css({
"display": "none"
})
$(".OC2-memberTable div.OC2-tableStatus").css({
"width": "55px",
"text-align": "left"
})
$(".OC2-memberTable div.OC2-tableMember").css({
"width": "120px",
})
$(".OC2-memberTable div.OC2-tableCrime").css({
"width": "210px",
})
$(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
"width": "220px",
})
$(".OC2-statusText").not(".OC2-hideSmall").css({
"display": ""
})
$(".OC2-memberTable div.OC2-tableCrimePosition").css({
"width": "70px",
"font-size": "10px",
})
$(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
"width": "220px",
})
$(".OC2-memberTable div.OC2-tableCrimeMemberStatus").css({
"width": "90px",
"text-align": "left"
})
$(".OC2-memberTable div.OC2-tableCrimeMemberSuccess").css({
"width": "18px",
"text-align": "center"
})
$(".OC2-memberTable div.OC2-tableCrimeMemberItem").css({
"width": "30px",
"text-align": "center"
})
}
function styleTableTinyScreen() {
$(".OC2-memberTable li.OC2-crimeLi").not(".OC2-memberTable li[class*='OC2-titleLi']").css({
"padding-left": "15px",
"width": containerMaxWidth
})
$(".OC2-hideSmall").css({
"display": "none"
})
$(".OC2-tableCrimeMemberCount").css({
"width": "40px"
})
$(".OC2-memberTable div.OC2-tableStatus").css({
"width": "90px",
"text-align": "left"
})
$(".OC2-memberTable div.OC2-tableCrime").css({
"width": "190px",
})
$(".OC2-memberTable div.OC2-tableCrimeMemberName").css({
"width": "165px",
})
$(".OC2-statusText").not(".OC2-hideSmall").css({
"display": ""
})
$(".OC2-memberTable li.OC2-crimeMemberLi").css({
"padding-left": "18px",
})
$(".OC2-memberTable li.OC2-memberAvailable").css({
"padding-left": "12px"
})
$(".OC2-memberTable li.OC2-crimeLi").not(".OC2-memberTable li[class*='OC2-titleLi']").css({
"padding-left": "12px"
})
}
function styleTableBigScreen() {
$(".OC2-hideSmall").css({
"display": ""
})
$(".OC2-statusText").not(".OC2-hideSmall").css({
"display": "none"
})
}
function styleOCNotifier() {
$(".OC2-sidebarNotice a").css({
"text-decoration": "none",
"color": "inherit"
})
$(".OC2-sidebarNotice .OC2-redtext").css({
"text-decoration": "none",
"color": "rgb(255, 121, 76)"
})
}
//main functions
/* if page is the crimes 2.0 page
* -> if memberViewer table does NOT exist, get data and fill table
* -> otherwise, show the table
* -> otherwise, hide the table
*/
async function hashChangeFunction() {
if (checkCrimesPage()) {
if (!$(".OC2-memberViewer .OC2-memberTable")[0]) {
generateInsertHTML();
if (!myAPIData) {
try {
let _successfulGetAPIData = await getAndAnalyzeAPIData()
if (_successfulGetAPIData.error) {
$(".OC2-memberTable").hide()
$(".OC2-memberTableErrorDisplay").html(`<span style="margin-left: 20px">Error occured: ${_successfulGetAPIData.error.error}. Please visit the <a href="https://www.torn.com/OC2_Settings_Page" target="_new" style="color: inherit; font-weight: bold; text-decoration: underline">Settings Page</a> to set up an API key</span>`)
$(".OC2-memberTableErrorDisplay").show()
} else {
putMemberInfoIntoTable()
}
} catch(_err) {
return _err
}
}
} else {
$(".OC2-memberViewer").show()
}
} else {
if ($(".OC2-memberViewer .OC2-memberTable")[0]) {
$(".OC2-memberViewer").hide();
}
}
}
async function runOnceFunction() {
//load saved data
await getUserSettings()
await getAPIKey()
//get Allenone's API compatibility
if (userSettings.showAllenoneScript == "torn-probability-hide") {
//do nothing
} else {
await getTornProbabilitySupportedScenarios() //run this by default
}
//prepare settings page if on correct URL
if ($(location).attr("pathname").search("OC2_Settings_Page") >= 0) {
$(document).prop('title', 'OC2 Overview - Settings | TORN');
$("div.main-wrap").removeClass("error-404")
$("div.content-title #skip-to-content").text("OC 2.0 Overview: Settings")
prepareSettingsPage()
getSavedValues()
settingsFillSelect()
}
//insert member overview
if (checkCrimesPage() || await checkTravelFactionPage()) {
if (!$(".OC2-memberViewer .OC2-memberTable")[0]) {
generateInsertHTML()
if (!myAPIData) {
try {
let _successfulGetAPIData = await getAndAnalyzeAPIData()
if (_successfulGetAPIData.error) {
$(".OC2-memberTable").hide()
$(".OC2-memberTableErrorDisplay").html(`<span style="margin-left: 20px">Error occured: ${_successfulGetAPIData.error.error}. Please visit the <a href="https://www.torn.com/OC2_Settings_Page" target="_new" style="color: inherit; font-weight: bold; text-decoration: underline">Settings Page</a> to set up an API key</span>`)
$(".OC2-memberTableErrorDisplay").show()
} else {
putMemberInfoIntoTable()
}
} catch(_err) {
return _err
}
}
} else {
$(".OC2-memberViewer").show()
}
}
//sidebar notifier, but not if the sidebar doesn't exist
if (userSettings.showSidebarOC == "sidebar-show") {
if ( ($("div[class*='sidebar_'][class*='desktop_']")[0]) && (!isPDA()) ) {
if (!myAPIData) {
let _successfulGetAPIData = await getAndAnalyzeAPIData()
if (_successfulGetAPIData.error) {
return
}
}
insertOCNotifier()
}
}
}
//taken from stackoverflow https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists because mutation observer confuses me
//needed because the faction page info only loads after the document is ready
function waitForElm(selector) {
return new Promise(resolve => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
observer.disconnect();
resolve(document.querySelector(selector));
}
});
// If you get "parameter 1 is not of type 'Node'" error, see https://stackoverflow.com/a/77855838/492336
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
function checkWindowWidth() {
if (_isWindowNormal.matches) {
containerMaxWidth = "784px"
containerBigMaxWidth = "976px"
} else if (_isWindowSmallish.matches) {
containerMaxWidth = "784px"
containerBigMaxWidth = "578px"
}else if (_isWindowSmall.matches) {
containerMaxWidth = "386px"
containerBigMaxWidth = "578px"
} else if (_isWindowTiny.matches) {
containerMaxWidth = "320px"
containerBigMaxWidth = "320px"
}
if (!isPDA()) {
styleTable()
$(".OC2-crimeMemberLi").not(".crimeMemberLiShow").css({
"display": "none"
})
}
}
_isWindowNormal.addEventListener("change", function() {
checkWindowWidth()
});
_isWindowSmallish.addEventListener("change", function() {
checkWindowWidth()
});
_isWindowSmall.addEventListener("change", function() {
checkWindowWidth()
});
_isWindowTiny.addEventListener("change", function() {
checkWindowWidth()
});
checkWindowWidth()
runOnceFunction()
$(window).on('hashchange', hashChangeFunction)
$("#dark-mode-state").on('change', modeChangeFunction)
function modeChangeFunction() {
colorDisplayMode = $("body#body").hasClass("dark-mode") ? "darkmode" : "lightmode"
styleTable()
checkDefaultHideState()
styleSettings()
}
/* ====
* Settings page stuff
* ====
*/
async function testAPIKey(_APIKey) {
$(".OC2-APITestResults div[id^=OC2-APITestResult-]").remove()
$(".OC2-APITestResults").append(`<div>Testing...</div>`)
let _insertHTML = ""
return await $.ajax({
dataType: "json",
url: (`https://api.torn.com/key/?selections=info&key=${_APIKey}`)
}).then( data => {
try {
let _testResults = {
"limited": data.access_level >= 2,
"basic": data.selections.faction.includes("basic"),
"crimes": data.selections.faction.includes("crimes"),
"members": data.selections.faction.includes("members"),
"items": data.selections.torn.includes("items"),
"final": false
}
_testResults.final = (_testResults.limited && _testResults.basic && _testResults.crimes && _testResults.members && _testResults.items)
let _insertHTML = (`
<div id="OC2-APITestResult-Limited" class="${_testResults.limited ? "color-green" : "color-red"}">Minimal Key (or higher) ${_testResults.limited ? "OK" : "X"}</div>
<div id="OC2-APITestResult-Basic" class="${_testResults.basic ? "color-green" : "color-red"}">/v2/factions/basic ${_testResults.basic ? "OK" : "X"}</div>
<div id="OC2-APITestResult-Crimes" class="${_testResults.crimes ? "color-green" : "color-red"}">/v2/factions/crimes ${_testResults.crimes ? "OK" : "X"}</div>
<div id="OC2-APITestResult-Members" class="${_testResults.members ? "color-green" : "color-red"}">/v2/factions/members ${_testResults.members ? "OK" : "X"}</div>
<div id="OC2-APITestResult-Items" class="${_testResults.items ? "color-green" : "color-red"}">/v1/torn/items ${_testResults.items ? "OK" : "X"}</div>
<div id="OC2-APITestResult-Final" class="${_testResults.final ? "color-green" : "color-red"}">${_testResults.final ? "API Key good to go!" : "API Key not usable!"}</div>
<div id="OC2-APITestResult-Final" class="${_testResults.final ? "color-green" : "color-red"}">${_testResults.crimes && _testResults.members ? "Save settings to enable Member Ignore List" : `You do not have faction API permission. Please ask your faction leader to give you a role with "Faction API access" permissions.`}</div>
`)
$(".OC2-APITestResults div").remove()
$(".OC2-APITestResults").append(_insertHTML)
} catch(err) {
$(".OC2-APITestResults div").remove()
$(".OC2-APITestResults").append(`<div id="OC2-APITestResult-Fail" class="color-red">Error: API Key not valid</div>`)
}
$(".OC2-APITestResults div[id^=OC2-APITestResult-]").css({
"margin-left": "15px"
})
$("div[id^=OC2-APITestResult-].color-green").css({
"color": colorObj.recolor.green[colorDisplayMode]
})
$("div[id^=OC2-APITestResult-].color-red").css({
"color": colorObj.recolor.red[colorDisplayMode]
})
$(".OC2-APITestButton").off().on("click", (event) => {
APITestClickEvent(event)
})
})
}
async function setSavedValues() {
let _userValues = {
"memberShow": $("input[name=OC2-display-choice-availMembers]:checked")[0].value,
"crimesShow": $("input[name=OC2-display-choice-crimes]:checked")[0].value, //crimes-hide / crimes-show
"sortType": $("input[name=OC2-display-choice-crimes-sort]:checked")[0].value, //time-asc / time-desc / level-asc / level-desc
"memberSort": $("input[name=OC2-display-choice-availMembers-sort]:checked")[0].value, //OC-asc / OC-desc / active-asc / active-desc
"lastOC_yellow": $(`input[id=OC-indicator-yellow]`).val(),
"lastOC_red": $(`input[id=OC-indicator-red]`).val(),
"lastActivity": $(`input[id=activity-indicator]`).val(),
"memberIgnoreList": displayIgnoreList,
"showSidebarOC": $("input[name=OC2-display-choice-sidebarShow]:checked")[0].value, //sidebar-show / sidebar-hide
"showNegativeTimes": $("input[name=OC2-display-choice-negative-timer]:checked")[0].value, //negative-timer-show / negative-timer-hide
"warnLowSuccessPercentage": $(`input[id=warn-low-success]`).val(),
"showProgressPercentage": $("input[name=OC2-display-crime-progress]:checked")[0].value, //progress-show / progress-hide
"showSelfSuccessRate": $("input[name=OC2-display-choice-self-success]:checked")[0].value,
"showAllenoneScript": $("input[name=OC2-display-choice-torn-probability]:checked")[0].value,
"memberDirectionality": $("input[name=OC2-display-choice-availMembers-order]:checked")[0].value,
}
await setUserSettings(_userValues)
await getUserSettings()
if ( $("#OC2-APITestResult-Final").hasClass("color-green") ) {
await saveAPIKey()
$("#OC2-addToIgnoreButton").show()
$("#OC2-ignoreMemberInput").prop("disabled", false)
$("#OC2-ignoreMemberSelect").prop("disabled", false)
$(".OC2-errorAPIKey").remove()
settingsFillSelect()
}
getSavedValues()
}
async function saveAPIKey() {
let _userKey = $("#OC2-APIInput").val()
await GM.setValue("CMR_OC2_APIKey", _userKey)
await getAPIKey()
}
async function replaceSavedValues() {
displayIgnoreList = []
userSettings = defaultUserSettings
getSavedValues()
}
function getSavedValues() {
$(`input[name=OC2-display-choice-availMembers][id=${userSettings?.memberShow}]`).prop("checked", true)
$(`input[name=OC2-display-choice-crimes][id=${userSettings?.crimesShow}]`).prop("checked", true)
$(`input[name=OC2-display-choice-crimes-sort][id=${userSettings?.sortType}]`).prop("checked", true)
$(`input[name=OC2-display-choice-availMembers-sort][id=${userSettings?.memberSort}]`).prop("checked", true)
$(`input[name=OC2-display-choice-sidebarShow][id=${userSettings?.showSidebarOC}]`).prop("checked", true)
$(`input[name=OC2-display-choice-negative-timer][id=${userSettings?.showNegativeTimes}]`).prop("checked", true)
$(`input[name=OC2-display-crime-progress][id=${userSettings?.showProgressPercentage}]`).prop("checked", true)
$(`input[id=OC-indicator-yellow]`).val(userSettings?.lastOC_yellow)
$(`input[id=OC-indicator-red]`).val(userSettings?.lastOC_red)
$(`input[id=activity-indicator]`).val(userSettings?.lastActivity)
$(`input[id=warn-low-success]`).val(userSettings?.warnLowSuccessPercentage)
$(`input[name=OC2-display-choice-self-success][id=${(userSettings?.showSelfSuccessRate || defaultUserSettings.showSelfSuccessRate )}]`).prop("checked", true)
$(`input[name=OC2-display-choice-torn-probability][id=${(userSettings?.showAllenoneScript || defaultUserSettings.showAllenoneScript )}]`).prop("checked", true)
$(`input[name=OC2-display-choice-availMembers-order][id=${(userSettings?.memberDirectionality || defaultUserSettings.memberDirectionality )}]`).prop("checked", true)
fillMemberIgnoreList()
}
function APITestClickEvent(_event) {
$("#OC2-APIInput").val( $("#OC2-APIInput").val().trim() ) //trim trailing spaces
let _testKey = $("#OC2-APIInput").val()
testAPIKey(_testKey)
}
async function deleteAPIKey() {
$(".OC2-errorAPIKey").remove()
$(".OC2-APITestResults div").remove()
if (isPDA()) {
$(".OC2-APITestResults").append(`<div class="color-yellow">TornPDA: Unable to remove API Key via script because it's saved on TornPDA itself.</div>`)
return
}
GM.setValue("CMR_OC2_APIKey", null)
APIKey = null
$(".OC2-APITestResults").append(`<div class="color-red">API Key removed from script memory. If you wish to be sure that your API key is secure, delete the provided key from torn's settings page.</div>`)
$("#OC2-APIInput").val(APIKey? APIKey : "")
settingsFillSelect()
}
function prepareSettingsPage() {
let _displayAPIKey = "API Key saved in TornPDA"
if (!isPDA()) {
_displayAPIKey = APIKey? APIKey : ""
}
let _injectHTML = (`
<div id="OC2-Settings" class="category-wrap m-top10">
<div class="title-black top-round t-overflow">OC 2.0 Settings</div>
<div class="cont-gray OC2-settingsTable"><ul class="table-body">
<li class="table-cell OC2-settingsTitle"><div class="OC2-titleCell OC2-fancyBg">API Key</div></li>
<li class="table-cell OC2-settingsSection">
<div class="OC2-settingsCell"><input id="OC2-APIInput" type="textbox" style="line-height: 14px; padding: 10px 8px" value="${_displayAPIKey}" /><div id="OC2-APITestButton" class="OC2-button">Test API Key</div></div>
</li>
<li class="table-cell OC2-settingsSection">
<div class="OC2-settingsCell"><div id="OC2-deleteAPIKeyButton" class="OC2-button">Delete API Key</div></div>
</li>
<li class="table-cell OC2-settingsSection">
<div class="OC2-settingsCell OC2-APITestResults">
</div>
</li>
<li class="table-cell OC2-horizLine"></li>
<li class="table-cell OC2-memberAvailable OC2-settingsTitle"><div class="OC2-titleCell OC2-fancyBg">Preferences</div></li>
<li class="table-cell OC2-settingsSection">
<div class="OC2-settingsCell">
<div class="OC2-settingsSubTitle">Default View</div>
<fieldset class="OC2-choice">
<legend><span title="(TornTools also provides this)<br />Adds a text indicator to the sidebar on your current OC, and gives a warning if you are not in an OC.<br />Note: Will cause the script to request one more API call per page load if the sidebar is visible.<br />Will have no effect on TornPDA users, or users with small screens." class="OC2-infoHover">${moreInfoHover}</span>Show OC information in sidebar? </legend>
<div class="OC2-choice-buttons">
<input type="radio" name="OC2-display-choice-sidebarShow" id="sidebar-show" value="sidebar-show" /><label for="sidebar-show">Yes</label>
<input type="radio" name="OC2-display-choice-sidebarShow" id="sidebar-hide" value="sidebar-hide" /><label for="sidebar-hide">No</label>
</div>
</fieldset>
<fieldset class="OC2-choice">
<legend> <span title="If enabled, crime timers will turn <span style='color:${colorObj.recolor.red[colorDisplayMode]}'>red</span> and start counting up if they are delayed.<br />If disabled, crime timers will stay at <span style='color:${colorObj.recolor.red[colorDisplayMode]}'>0:00:00:00</span> if they are delayed." class="OC2-infoHover">${moreInfoHover}</span>Show countup timer if crime is delayed?</legend>
<div class="OC2-choice-buttons">
<input type="radio" name="OC2-display-choice-negative-timer" id="negative-timer-show" value="negative-timer-show" /><label for="negative-timer-show">Yes</label>
<input type="radio" name="OC2-display-choice-negative-timer" id="negative-timer-hide" value="negative-timer-hide" /><label for="negative-timer-hide">No</label>
</div>
</fieldset>
<fieldset class="OC2-choice">
<legend> <span title="If enabled, success chance of the API key holder (which should be you) will be shown in all empty crime slots.<br />If disabled, the success chance will not be shown." class="OC2-infoHover">${moreInfoHover}</span>Show self success chance in empty crimes?</legend>
<div class="OC2-choice-buttons">
<input type="radio" name="OC2-display-choice-self-success" id="self-success-show" value="self-success-show" /><label for="self-success-show">Yes</label>
<input type="radio" name="OC2-display-choice-self-success" id="self-success-hide" value="self-success-hide" /><label for="self-success-hide">No</label>
</div>
</fieldset>
<fieldset class="OC2-choice">
<legend><span title="This will only show success chances for crimes that have their routes mapped out.<br />Please check out the link to Allenone's forum post for more details." class="OC2-infoHover">${moreInfoHover}</span>Show overall success chance of full crimes? (uses <a class="settingshref" href="https://www.torn.com/forums.php#/p=threads&f=67&t=16449999" target="_blank">Allenone's API</a>)</legend>
<div class="OC2-choice-buttons">
<input type="radio" name="OC2-display-choice-torn-probability" id="torn-probability-show" value="torn-probability-show" /><label for="torn-probability-show">Yes</label>
<input type="radio" name="OC2-display-choice-torn-probability" id="torn-probability-hide" value="torn-probability-hide" /><label for="torn-probability-hide">No</label>
</div>
</fieldset>
<fieldset class="OC2-choice">
<legend><span title="If enabled, the list of members not currently in an OC will be shown when the script loads.<br />If disabled, you will need to click on the 'show members' button to see this section." class="OC2-infoHover">${moreInfoHover}</span>On load, show "Members not in an OC" section? </legend>
<div class="OC2-choice-buttons">
<input type="radio" name="OC2-display-choice-availMembers" id="member-show" value="member-show" /><label for="member-show">Yes</label>
<input type="radio" name="OC2-display-choice-availMembers" id="member-hide" value="member-hide" /><label for="member-hide">No</label>
</div>
</fieldset>
<fieldset class="OC2-choice">
<legend><span title="Default sort behaviour for list of members not currently in OCs.<br />Options:<br /> - OC: Asc = Members with most recent OCs will be first<br> - OC: Desc = Members that have not participated recently will be first<br /> - Active: Asc = Most recently active members will be first<br /> - Active: Desc = Members that have not been active recently will be shown first" class="OC2-infoHover">${moreInfoHover}</span>Default sort for "Members not in an OC"? </legend>
<div class="OC2-choice-buttons">
<input type="radio" name="OC2-display-choice-availMembers-sort" id="OC-desc" value="OC-desc" /><label for="OC-desc">OC: Asc </label>
<input type="radio" name="OC2-display-choice-availMembers-sort" id="OC-asc" value="OC-asc" /><label for="OC-asc">OC: Desc</label>
<input type="radio" name="OC2-display-choice-availMembers-sort" id="active-asc" value="active-asc" /><label for="active-asc">Active: Asc</label>
<input type="radio" name="OC2-display-choice-availMembers-sort" id="active-desc" value="active-desc" /><label for="active-desc">Active: Desc</label>
</div>
</fieldset>
<fieldset class="OC2-choice">
<legend><span title="Will only take effect if your screen size is large enough for 2 columns.<br />'left-to-right, row then column' = Members will be shown left-to-right, row by row.<br />'top-to-bottom, column then row' = Members will be shown down the left column, then down the right column." class="OC2-infoHover">${moreInfoHover}</span>Ordering of member list (if 2 columns are shown)? </legend>
<div class="OC2-choice-buttons">
<input type="radio" name="OC2-display-choice-availMembers-order" id="row-column" value="row-column" /><label for="row-column">left-to-right, row then column</label>
<input type="radio" name="OC2-display-choice-availMembers-order" id="column-row" value="column-row" /><label for="column-row">top-to-bottom, column then row</label>
</div>
</fieldset>
<fieldset class="OC2-choice">
<legend><span title="If enabled, the list of crimes will be shown when the script loads.<br />If disabled, you will need to click on the 'show crimes' button to see this section." class="OC2-infoHover">${moreInfoHover}</span>On load, show "Crimes" section? </legend>
<div class="OC2-choice-buttons">
<input type="radio" name="OC2-display-choice-crimes" id="crimes-show" value="crimes-show" /><label for="crimes-show">Yes</label>
<input type="radio" name="OC2-display-choice-crimes" id="crimes-hide" value="crimes-hide" /><label for="crimes-hide">No</label>
</div>
</fieldset>
<fieldset class="OC2-choice">
<legend><span title="Default sort behaviour for crimes.<br />Options:<br /> - Time: Asc = Crimes near completion/expiry/requiring attention will be shown first<br /> - Time: Desc = The opposite of the above.<br /> - Level: Asc = Lowest level crimes will be shown first<br /> - Level: Desc = Highest level crimes will be shown first." class="OC2-infoHover">${moreInfoHover}</span>Default sort for "Crimes"? </legend>
<div class="OC2-choice-buttons">
<input type="radio" name="OC2-display-choice-crimes-sort" id="time-asc" value="time-asc" /><label for="time-asc">Time: Asc</label>
<input type="radio" name="OC2-display-choice-crimes-sort" id="time-desc" value="time-desc" /><label for="time-desc">Time: Desc</label>
<input type="radio" name="OC2-display-choice-crimes-sort" id="level-asc" value="level-asc" /><label for="level-asc">Level: Asc</label>
<input type="radio" name="OC2-display-choice-crimes-sort" id="level-desc" value="level-desc" /><label for="level-desc">Level: Desc</label>
</div>
</fieldset>
<fieldset class="OC2-choice">
<legend><span title="Shows the progress of each member's crime planning completion rate next to their names." class="OC2-infoHover">${moreInfoHover}</span>Show member crime planning %? </legend>
<div class="OC2-choice-buttons">
<input type="radio" name="OC2-display-crime-progress" id="progress-show" value="progress-show" /><label for="progress-show">Yes</label>
<input type="radio" name="OC2-display-crime-progress" id="progress-hide" value="progress-hide" /><label for="progress-hide">No</label>
</div>
</fieldset>
</div>
</li>
<li class="table-cell OC2-horizLine"></li>
<li class="table-cell OC2-settingsSection">
<div class="OC2-settingsCell">
<div class="OC2-settingsSubTitle">Highlight & Indicator settings</div>
<ul>
<li><div class="OC2-settingsText"><div class="OC2-settingsLabel"><span title="The crime icon next to members' names will turn <span style='color:${colorObj.recolor.yellow[colorDisplayMode]}'>yellow</span> if their last OC participation is longer than this time" class="OC2-infoHover">${moreInfoHover}</span>Time since last OC for 'yellow' highlight:</div><input id="OC-indicator-yellow" type="textbox" style="line-height: 12px; padding: 5px" placeholder="24" size="10" /> hours</div></li>
<li><div class="OC2-settingsText"><div class="OC2-settingsLabel"><span title="The crime icon next to members' names will turn <span style='color:${colorObj.recolor.red[colorDisplayMode]}'>red</span> if their last OC participation is longer than this time" class="OC2-infoHover">${moreInfoHover}</span>Time since last OC for 'red' highlight:</div><input id="OC-indicator-red" type="textbox" style="line-height: 12px; padding: 5px" placeholder="48" size="10" /> hours</div></li>
<li><div class="OC2-settingsText"><div class="OC2-settingsLabel"><span title="Member's name will turn <span style='color:${colorObj.recolor.red[colorDisplayMode]}'>red</span> if they are inactive for longer than this time" class="OC2-infoHover">${moreInfoHover}</span>Time since last activity for 'inactive' indicator:</div><input id="activity-indicator" type="textbox" style="line-height: 12px; padding: 5px" palceholder="96" size="10" /> hours</div></li>
<li><div class="OC2-settingsText"><div class="OC2-settingsLabel"><span title="A <span style='color:${colorObj.recolor.red[colorDisplayMode]}'>[%]</span> icon will appear next to crimes that have a member with a success rate lower than this number" class="OC2-infoHover">${moreInfoHover}</span>Member slot 'low success warning' threshold:</div><input id="warn-low-success" type="textbox" style="line-height: 12px; padding: 5px" palceholder="50" size="10" /> %</div></li>
</ul>
</div>
</li>
<li class="table-cell OC2-horizLine"></li>
<li class="table-cell OC2-settingsSection">
<div class="OC2-settingsCell">
<div class="OC2-settingsSubTitle"><span title="Members on the ignore list will not be counted as 'available members' and their names will not be shown with the list of available members" class="OC2-infoHover">${moreInfoHover}</span>Member Ignore List</div>
<div class="OC2-settingsText">
<div class="OC2-memberIgnoreWrapper">Add member:
<input id="OC2-ignoreMemberInput" type="textbox" />
<select id="OC2-ignoreMemberSelect">
<option class="default-option" value="" selected></option>
</select>
<div id="OC2-addToIgnoreButton" class="OC2-button">Add</div>
</div>
</div>
<div class="OC2-settingsText">Ignored Members:
<ul class="OC2-memberIgnoreList">
<li class="OC2-ignoreTitles" style="font-weight: bold; margin-bottom: 5px"><div class="OC2-ignoreName">Member Name [id]</div> <div class="OC2-ignoreTime">Last Active Time</div><div class="OC2-ignoreButtons">Action</div></li>
</ul>
</div>
</div>
</li>
<li class="table-cell OC2-horizLine"></li>
<li class="table-cell OC2-settingsSection">
<div class="OC2-settingsCell">
<div class="OC2-buttonDiv">
<div id="OC2-APIResetButton" class="OC2-button">Reset to Default</div><div id="OC2-APISaveButton" class="OC2-button">Save Changes</div>
</div>
<div id="OC2-buttonResult"></div>
</div>
</li>
</ul></div>
<div class="OC2-memberTableFooter OC2-settingsFooter"></div>
</div>
`)
$("div.main-wrap").html(_injectHTML)
//onclick functions
$("#OC2-APITestButton").off().on("click", (event) => {
APITestClickEvent(event)
})
$("#OC2-APISaveButton").off().on("click", (event) => {
setSavedValues()
.then( () => {
$("#OC2-buttonResult").html("Settings saved!")
if ($("#OC2-buttonResult").is(":visible")) {
$("#OC2-buttonResult").slideUp("fast")
}
$("#OC2-buttonResult").slideDown("slow")
})
})
$("#OC2-APIResetButton").off().on("click", (event) => {
replaceSavedValues()
.then( () => {
$("#OC2-buttonResult").html("Settings reset to default")
if ($("#OC2-buttonResult").is(":visible")) {
$("#OC2-buttonResult").slideUp("fast")
}
$("#OC2-buttonResult").slideDown("slow")
})
})
$("#OC2-deleteAPIKeyButton").off().on("click", (event) => {
deleteAPIKey()
})
if (isPDA()) {
$("#OC2-APIInput").prop("disabled", true)
$("#OC2-APITestButton").off()
$("#OC2-deleteAPIKeyButton").off()
}
$("#OC2-ignoreMemberInput").off()
$("#OC2-ignoreMemberSelect").off()
$("#OC2-ignoreMemberInput").on("keyup", (event) => {
$("#OC2-ignoreMemberSelect option").not(".default-option").remove()
let _insertOption = ""
let _displayMemberList = myAPIData.members.filter((member) => (member.name.toLowerCase()).search(event.currentTarget.value.toLowerCase()) > -1)
_displayMemberList.forEach( (member) => {
_insertOption += (`<option value="${member.id}">${member.name} [${member.id}]</option>`)
})
$("#OC2-ignoreMemberSelect").append(_insertOption)
$("#OC2-ignoreMemberSelect").attr("size", Math.min(7,_displayMemberList.length+1))
})
$("#OC2-ignoreMemberInput").on("focus", (event) => {
$("#OC2-ignoreMemberSelect").attr("size", 7)
})
$("#OC2-ignoreMemberInput").on("blur", (event) => {
if (event.relatedTarget != $("#OC2-ignoreMemberSelect")[0]) {
$("#OC2-ignoreMemberSelect").attr("size", 0)
}
})
$("#OC2-ignoreMemberSelect").on("change", (event) => {
$("#OC2-ignoreMemberInput").val($("#OC2-ignoreMemberSelect :selected").text())
$("#OC2-ignoreMemberSelect").attr("size", 0)
})
$("#OC2-addToIgnoreButton").off().on("click", (event) => {
let _memberToIgnore = $("#OC2-ignoreMemberSelect :selected").val().toString()
if (displayIgnoreList.includes(_memberToIgnore)) {
return //do nothing
} else {
displayIgnoreList.push(_memberToIgnore)
fillMemberIgnoreList()
}
})
styleSettings()
}
function styleSettings() {
colorDisplayMode = $("body#body").hasClass("dark-mode") ? "darkmode" : "lightmode"
$(".settingshref").css({
"color": colorObj.link[colorDisplayMode],
"text-decoration": "none"
})
$(".settingshref").hover(
function() {
$(this).css({
"text-decoration": "underline"
})
},
function() {
$(this).css({
"text-decoration": "none"
})
})
$(".OC2-buttonDiv").css({
"display": "flex",
"flex-direction": "row",
"justify-content": "center",
//"width": (_isWindowTiny.matches)? parseInt(containerBigMaxWidth) - 30 + "px" : $(this).parent().width() - 50 + "px"
})
$("#OC2-buttonResult").css({
"margin": "3px auto 8px -8px",
"text-align": "center",
"padding": "5px 0",
"background-color": colorObj.userindicatorbg[colorDisplayMode],
"display": "none",
"width": "100%"
})
$(".OC2-APITestResults").css({
"display": "flex",
"flex-direction": "row",
"flex-wrap": "wrap"
})
$(".OC2-choice").css({
"display": "flex",
"flex-direction": "row",
"flex-wrap": "wrap"
})
$(".OC2-settingsSubTitle").css({
"margin-bottom": "10px",
"font-weight": "bold"
})
$(".OC2-settingsLabel").css({
"width": "50%",
"display": "inline-block"
})
$("fieldset.OC2-choice").css({
"display": "inline-block",
"width": "100%",
"margin": "5px 0 5px 10px"
})
$("fieldset.OC2-choice .OC2-choice-buttons").css({
"display": "flex",
"flex-direction": "row",
})
$("fieldset.OC2-choice legend").css({
"float": "left",
"width": "50%"
})
$("span.OC2-infoHover").css({
"padding-right": "8px"
})
$("fieldset.OC2-choice label").css({
"display": "inline-block",
"margin": "0 10px 0 5px"
})
$(".OC2-settingsText").css({
"margin-left": "11px",
})
$("#OC2-Settings ul.table-body").css({
"display": "flex",
"flex-direction": "row",
"flex-wrap": "wrap"
})
$("#OC2-Settings li.table-cell").css({
"display": "flex",
"flex-direction": "row",
"width": "100%",
"font-size": "12px",
})
$("#OC2-Settings .OC2-titleCell.OC2-fancyBg").css({
"width": "100%",
"background": "repeating-linear-gradient(90deg, #2e2e2e, #2e2e2e 2px, #282828 0, #282828 4px)",
"padding": "5px 0",
"padding-left": "10px",
"font-weight": "bold",
"color": colorObj.fancyBg[colorDisplayMode]
})
$("#OC2-Settings .OC2-settingsCell").css({
"padding": "5px 0",
"margin-left": "15px",
"font-weight": "normal",
"width": "100%"
})
$(".OC2-button").css({
"margin-left": "5px",
"padding": "5px 10px",
"text-align": "center",
"display": "inline-block",
"background": colorObj.buttons.background[colorDisplayMode],
"cursor": "pointer",
"color": colorObj.buttons.textcolor[colorDisplayMode]
})
$(".OC2-button").on("mouseenter", function(event) {
$(event.currentTarget).css({
"background": colorObj.buttons.hovercolor[colorDisplayMode]
})
})
$(".OC2-button").on("mouseleave", function(event) {
$(event.currentTarget).css({
"background": colorObj.buttons.background[colorDisplayMode]
})
})
$(".OC2-memberIgnoreWrapper").css({
"position": "relative",
"margin-bottom": "15px",
"margin-top": "10px",
"display": "inline-block"
})
$("#OC2-addToIgnoreButton").css({
"position": "absolute",
"left": "310px",
"top": "-4px"
})
$("#OC2-ignoreMemberSelect").css({
"position": "absolute",
"top": "0px",
"left": "105px",
"width": "200px",
})
$("#OC2-ignoreMemberInput").css({
"position": "absolute",
"top": "-7px",
"left": "100px",
"width": "200px",
"padding": "5px",
"z-index": "10"
})
$(".OC2-infoHover").css({
"vertical-align": "middle"
})
$(".OC2-settingsFooter").css({
"padding-bottom": "100px",
"height": "10px"
})
styleMemberIgnoreList()
if (_isWindowTiny.matches || _isWindowSmall.matches ) {
styleSettingsTiny()
}
if (isPDA()) {
$("#OC2-ignoreMemberInput").off()
$("#OC2-ignoreMemberInput").css({
"display": "none"
})
$("#OC2-APITestButton").hide()
$("#OC2-deleteAPIKeyButton").hide()
}
$(".OC2-horizLine").css({
"border-bottom": "1px solid rgb(34,34,34)",
"width": $(this).parent().width() + "px",
"box-sizing": "border-box",
"height": "3px",
})
}
function styleSettingsTiny() {
$(".OC2-settingsCell").css({
"margin-left": "10px"
})
$(".OC2-settingsLabel").css({
"width": "90%",
})
$("fieldset.OC2-choice legend").css({
"float": "left",
"width": "90%"
})
$("fieldset.OC2-choice").css({
"margin": "10px 0 10px 5px",
})
$("fieldset.OC2-choice legend").css({
"float": "none",
"margin-bottom": "5px",
})
$("fieldset.OC2-choice .OC2-choice-buttons").css({
"margin-left": "10px",
})
$("fieldset.OC2-choice label").css({
"margin": "0 3px 0 3px",
"width": "100%"
})
$(".OC2-settingsText").css({
"margin-left": "5px",
"margin-bottom": "5px"
})
$("#OC2-addToIgnoreButton").css({
"left": "240px",
"top": "-4px"
})
$("#OC2-ignoreMemberSelect").css({
"top": "0px",
"left": "85px",
"width": "150px",
})
$("#OC2-ignoreMemberInput").css({
"top": "-7px",
"left": "80px",
"width": "150px",
"padding": "5px",
"z-index": "10"
})
$(".OC2-memberIgnoreList").css({
"margin": "5px 0 0 0",
})
$(".OC2-memberIgnoreList li").not("li.OC2-ignoreTitles").css({
"align-items": "center"
})
$(".OC2-ignoreName").css({
"width": "120px"
})
$(".OC2-ignoreTime").css({
"width": "120px",
})
}
function styleMemberIgnoreList() {
colorDisplayMode = $("body#body").hasClass("dark-mode") ? "darkmode" : "lightmode"
$(".OC2-memberIgnoreList a").css({
"color": "inherit"
})
$(".OC2-memberIgnoreList").css({
"display": "flex",
"flex-direction": "row",
"margin": "5px 0 0 10px",
"flex-wrap": "wrap"
})
$(".OC2-memberIgnoreList li").css({
"display": "flex",
"flex-direction": "row",
"width": parseInt(containerBigMaxWidth) + "px",
"padding": "3px 0",
"align-items": "center"
})
$(".OC2-ignoreName").css({
"width": "200px"
})
$(".OC2-ignoreTime").css({
"width": "200px"
})
$(".OC2-ignoreButtons .OC2-button").css({
"margin-top": "3px",
"margin-left": "-10px",
"padding": "5px 10px",
"text-align": "center",
"display": "inline-block",
"background": colorObj.buttons.background[colorDisplayMode],
"cursor": "pointer",
"color": colorObj.buttons.textcolor[colorDisplayMode]
})
$(".OC2-button").on("mouseenter", function(event) {
$(event.currentTarget).css({
"background": colorObj.buttons.hovercolor[colorDisplayMode]
})
})
$(".OC2-button").on("mouseleave", function(event) {
$(event.currentTarget).css({
"background": colorObj.buttons.background[colorDisplayMode]
})
})
if (_isWindowSmall.matches || _isWindowTiny.matches ) {
styleSettingsTiny()
}
}
function fillMemberIgnoreList() {
$(".OC2-memberIgnoreList li").not("li.OC2-ignoreTitles").remove()
//this breaks easily lol
if (!userSettings.memberIgnoreList) {
return
}
if (!displayIgnoreList) {
return
}
if (displayIgnoreList.length == 0) {
return
}
displayIgnoreList.forEach( (_id) => {
_id = _id.toString()
//remove stray memberIDs if they are not part of the faction
if (!memberInfo[_id]) {
displayIgnoreList = displayIgnoreList.filter(item => item !== _id)
return
}
$(".OC2-memberIgnoreList").append(`<li data-memberid="${_id}">
<div class="OC2-ignoreName">${memberInfo[_id].name} [${_id}]</div>
<div class="OC2-ignoreTime">${memberInfo[_id].last_action.relative}</div>
<div class="OC2-ignoreButtons"><div class="OC2-button">Remove</div></div>
</li>`)
$(`li[data-memberid=${_id}] div.OC2-button`).off().on("click", (event) => {
displayIgnoreList = displayIgnoreList.filter(item => item !== _id)
fillMemberIgnoreList()
})
})
styleMemberIgnoreList()
}
async function settingsFillSelect() {
if (!myAPIData) {
let _APITest = await getAndAnalyzeAPIData()
if (_APITest.error) {
$("#OC2-addToIgnoreButton").hide()
$("#OC2-ignoreMemberInput").prop("disabled", true)
$("#OC2-ignoreMemberSelect").prop("disabled", true)
$(".OC2-memberIgnoreWrapper").parent().prepend(`<div class="color-red OC2-errorAPIKey">This function requires an API key to be registered</div>`)
return
}
}
let _insertOption = ""
let _displayMemberList = myAPIData.members
_displayMemberList.sort( (a,b) => (a.name).localeCompare(b.name))
_displayMemberList.forEach( (member) => {
_insertOption += (`<option value="${member.id}">${member.name} [${member.id}]</option>`)
})
$("#OC2-ignoreMemberSelect").append(_insertOption)
//fill up displayIgnoreList, also syntax is like this as a PDA fix
if (userSettings.memberIgnoreList) {
displayIgnoreList = userSettings.memberIgnoreList
} else {
displayIgnoreList = []
}
fillMemberIgnoreList()
}