We need TW players to complete the article of the region-locked game Fingertips Breakout.
You don't need an account to add and correct this Wiki. Learn how to contribute, browse Bounties and join our Discord server.

MediaWiki:Gadget-SkillForm.js

Welcome to IOP Wiki. This website is maintained by the Girls' Frontline community and is free to edit by anyone.
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* Works in conjunction with Template:SkillPanel, which will have previously transferred the content from each skill subpage into hidden tables of class skilldataraw.

1. Root: Lists all "skillform" sections in the page and run the setup on each of them
2. initButtons: Populate tabButtonLane with as many tabs as needed based on the game. With convertSkillData, define data for each tab by copying corresponding data from skilldataraw tables.
3. convertSkillData: Converts HTML table into JS data. Preserve HTML tags only for games that need it.
4. initSkillForm: Generate the level selector. If on an assimilated unit page, also generate the "Max Analysis" checkbox.  Assign method display_data to the selector and checkbox. Assign method skillformButtonHandler to the tabs.
5. skillformButtonHandler: Changes active tab and passes relevant tabs data to display_data
6. display_data: Based on data associated with the tab, populate level selector with level numbers. Update skill name and icon to the skillcontent display. Update description with calculateDescriptionText. Updates Max Analysis checkbox visibility. Update skill conditions with calculateConditionText.
7. calculateDescriptionText: Parse variables in the description text and assign necessayr values based on the level selector and max analysis checkbox.
8. calculateConditionText: Define skill conditions (cost, cooldown...) based on the properties declared in the data.

Test cases:
*Jill (complex formatting, cooldown, initial cooldown)
*AK-74M (line breaks)
*Nurse Fairy (support points+turns cooldown)
*Architect/Assimilated (Alt) (Max Analysis)
*Fern (Arma Inscripta, seconds of cooldown)
*Klukai (cooldown, confectance points)
*/
RLQ.push(['jquery', function () {
	$(document).ready(function() {
		var skillforms = $('.skillform');
		if (skillforms.length > 0) {// Only 1 on normal pages
			skillforms.each(function(idx, element) {
				initButtons($(element).find('.tabButtonLane'), function(tabs) {
					initSkillForm(element, tabs);
					$(element).find('.tabButtonLane button').first().click();
				});
			});
		}
	});
}]);


function initButtons(buttonContainer, finishedHandler) {
	var skillform = buttonContainer.closest('.skillform');
	
	// Remove any existence of another button
	buttonContainer.empty();
	
	var skillCount = skillform.find('.skilldataraw').length;
	
	for (var idx=1; idx<=skillCount; idx++) {
		var idxNameAddOn = idx === 1 ? "" : ""+idx;
		var skillDataRawElement = skillform.find('.skilldataraw[data-skilldata-content="skill' + idxNameAddOn + 'data"]');
		var skillElement = skillDataRawElement.find('div');
	if (skillElement.length === 1) {
		var buttonData = convertSkillData(skillElement);
		
		var button = $('<button></button>');
		button.data('skilldata', buttonData);
		
		var skillEditLinks = skillDataRawElement.find('.skilleditlinksraw');
		if (skillEditLinks.length === 1) {
		button.data('skilleditlinks', skillEditLinks.html());
		}
		
		//Special tab names in Template:PNCHero
		if (skillform.hasClass('pnchero')){
			switch(idx){
				case 1:button.text('Passive'); break;
				case 2:button.text('Auto'); break;
				case 3:button.text('Ultimate'); break;
				default:button.text('Skill' + idx);
			}
		}else if (skillform.hasClass('gfl2')){//For Template:GFL2Doll
			switch(idx){
				case 1:button.text('Basic Attack'); break;
				case 2:button.text('Active 1'); break;
				case 3:button.text('Active 2'); break;
				case 4:button.text('Ultimate'); break;
				case 5:button.text('Passive'); break;
				default:button.text('Skill' + idx);
			}
		}else{//For GFL
		button.text('Skill' + idx);
		}
		
		buttonContainer.append(button);
	}
	}
	
	finishedHandler(buttonContainer.find('button'));
}

function initSkillForm(element, tabs) {
	var skillform = $(element);
					
	// selector is not possible in Wiki so we have to create it :/
	var levelSelector = skillform.find('.skilllevel select');
	if (levelSelector.length < 1) {
		levelSelector = $('<select></select>');
		levelSelector.addClass("gf-droplist");
		skillform.find('.skilllevel').append(levelSelector);
	}
	
	//The skilldata object will be properly bound to .skilllevel>select during display_data()
	levelSelector.on('change', function() { display_data(skillform, $(this).data('skilldata'), $(this).data('skilleditlinks')); });
	
	if (skillform.hasClass('assimilated')) {
		// Add checkbox for max analysis (visibility is controlled from display_data())
	var anamax = $('<label><input type="checkbox">&nbsp;Max Analysis</label>');
	skillform.find('.maxanalysis').append(anamax);
	//The skilldata object will be properly bound to .maxanalysis>input during display_data()
	anamax.find('input').on('change', function() { display_data(skillform, $(this).data('skilldata'), $(this).data('skilleditlinks')); });
	}
	
	tabs.click(function(evt) {
		skillformButtonHandler(evt.target);
	});
}

function skillformButtonHandler(element) {
	var currentButton = $(element);
	var skillform = currentButton.closest('.skillform');
	
	skillform.find('.tabButtonLane button').removeClass('tabButton-active');
	currentButton.addClass('tabButton-active');
	
	var convertedData = currentButton.data('skilldata');
	var skillEditLinks = currentButton.data('skilleditlinks');
	
	display_data(skillform, convertedData, skillEditLinks);
}

function convertSkillData(dataElement) {
    skill_data = {};
    
    dataElement.find('tbody tr').each(function(index, row) {
        var key = $(row).find('th').text().trim();
        var tds = $(row).find('td');
        
        if (tds.length === 1) {
            skill_data[key] = tds.first().html().trim(); // Keep HTML tags
        } else {
            var lines = tds.map(function(index, td) {
                return $(td).html().trim();
            }).get();
            skill_data[key] = lines;
        }
    });

    return skill_data;
}

function calculateDescriptionText(data, chosenLevelIdx) {
	var regex = /\(\$\w+\)/g;
	var resultText = data.text;
	
	if (!data.text || typeof data.text.match !== "function") {
		//console.log("debug", data);
		return resultText;
	}
	
	var vars = data.text.match(regex);
	//No parameters defined
	if(vars === null) {
		return resultText;
	}
	
	var i, var_name; // For i and var_name not being global :)
	
	if($('.maxanalysis input').is(':checked')) {
		for (i = 0; i < vars.length; i++) {
			var_name = vars[i].substring(2, vars[i].length - 1);// Trim beginning $( and trailing )
			var var_name_max = var_name + '_4';
			if (typeof data[var_name_max] !== 'undefined'){// If defined, use max analysis data instead of base data
				resultText = resultText.replace(vars[i], "<span class='skill-value'>" + data[var_name_max][chosenLevelIdx] + "</span>");
			} else {
				resultText = resultText.replace(vars[i], "<span class='skill-value'>" + data[var_name][chosenLevelIdx] + "</span>");
			}
		}
	} else {
		for (i = 0; i < vars.length; i++) {
			var_name = vars[i].substring(2, vars[i].length - 1);// Trim beginning $( and trailing )
			resultText = resultText.replace(vars[i], "<span class='skill-value'>" + data[var_name][chosenLevelIdx] + "</span>");
		}
	}
	
	return resultText;
}

function calculateConditionText(data, chosenLevelIdx) {
	/* Passive for skilldata without special properties. */
	var result = "Passive";
	
	/* Change name of skill points, default is for fairies */
	var pointsToUse = ' support';
	if ($('.skillform').hasClass('assimilated')) { pointsToUse = ' action'; }
	if ($('.skillform').hasClass('gfl2')) { pointsToUse = ' Confectance'; }
	
	if (data.hasOwnProperty('skill_cost')) {
		/* Fairys have both turn_cooldown and skill_cost. */
		if (Array.isArray(data.skill_cost) || typeof data.skill_cost === 'object') {
			result = data.skill_cost[chosenLevelIdx] + pointsToUse + ' point(s) per use';
		} else {
			result = data.skill_cost + pointsToUse + ' point(s) per use';
		}
		if (data.hasOwnProperty('turn_cooldown')) {
			if (Array.isArray(data.turn_cooldown) || typeof data.turn_cooldown === 'object') {
				result += ', ' + data.turn_cooldown[chosenLevelIdx] + ' turn cooldown';
			} else {
				result += ', ' + data.turn_cooldown + ' turn cooldown';
			}
		}
	} else if (data.hasOwnProperty('turn_cooldown')) {
		if (Array.isArray(data.turn_cooldown) || typeof data.turn_cooldown === 'object') {
			result = data.turn_cooldown[chosenLevelIdx] + ' turn cooldown';
		} else {
			result = data.turn_cooldown + ' turn cooldown';
		}
	} else if (data.hasOwnProperty('cooldown')) {
		/* cooldown and initial are used for T-Doll Skills. */
		result = data.cooldown[chosenLevelIdx] + 's cooldown';
		if (data.hasOwnProperty('initial')) {
			result += ', ' + data.initial + 's initial cooldown';
		}
	} else if (data.hasOwnProperty('activation')) {
		/* Skills/Buffs activated with a certain chance are defined by 'activation' */
		result = data.activation[chosenLevelIdx] + '% chance to activate';
	}
	return result;
}
 
function display_data(skillDataContainer, data, skilleditlinks) {
	if (data == null) {
		console.log("Skill called without data", data);
		return;
	}
	
	// Make sure skillcontainer is visible, as it is display:none by default
	skillDataContainer.find('.skillcontent').show();
	
	// Make sure Level Selector shows the fitting amount of levels
	var idx = 0;
	var skillLevels = 10;
	var levelSelector = skillDataContainer.find('.skilllevel select');
	
	if (data.hasOwnProperty('skilllevelcount')) {
		try {
			skillLevels = parseInt(data.skilllevelcount, 10);
		} catch (e) {
			console.log("Error parsing 'skilllevelcount'", e);
		}
	}
	
	if (levelSelector.find('option').length != skillLevels) {
		levelSelector.empty();
		for (idx=0; idx<skillLevels; idx++) {
			var opt = $('<option></option>');
			opt.text('Lv. ' + (idx+1));
			opt.attr('value', idx);
			if (idx == skillLevels-1) opt.prop('selected', true);
			levelSelector.append(opt);
		}
	}
	
	// We have to remember skilldata for the levelSelector
	levelSelector.data('skilldata', data);
			
	var name = skillDataContainer.find('.skillname');
	name.text(data.name);
	
	if (skilleditlinks) {
		var skilleditlinksDestination = skillDataContainer.find('.skilleditlinks');
	skilleditlinksDestination.html(skilleditlinks);
	}
	
	var iconImg = skillDataContainer.find('.skillicon img');
	var iconId = data.icon || "";
	// Normalize filenames
	iconId = iconId.replace(/ /g, '_');
	var iconFilename = "Icon_Skill_" + iconId + ".png";
	if (iconImg.attr('alt') != iconFilename) {
		iconImg.attr('alt', iconFilename);
		var wikiPath = "/images/" + gfUtils.createWikiPathPart(iconFilename) + iconFilename;
		var fallbackPath = "/images/thumb/5/5b/skill_backup.png/75px-skill_backup.png 1.5x, /images/5/5b/skill_backup.png 2x";
		iconImg.attr('src', wikiPath);
		if (iconId == '' || iconId == null) {
		iconImg.attr('srcset', fallbackPath);
	} else {
		// Check if icon actually exists as a file
		var img = new Image();
		img.src = wikiPath;
		img.onload = function(){ iconImg.attr('srcset', wikiPath + " 1.5x"); };
		img.onerror = function(){ iconImg.attr('srcset', fallbackPath); };
	}
	}
	
	if (skillDataContainer.hasClass('assimilated')) {
		// Show checkbox for max analysis only for assimilated first skill, else hide (hidden by default)
		var anamax = skillDataContainer.find('.maxanalysis');
		anamax.find('input').data('skilldata', data);
		if (skillDataContainer.find('.tabButton-active').text() == 'Skill1') {
			anamax.show();
		} else {
			anamax.hide();
		}
	}

	var chosenLevelIdx = skillDataContainer.find('.skilllevel select').val();
	var formatted_text = calculateDescriptionText(data, chosenLevelIdx);
	var description = skillDataContainer.find('.skilldesc');
	description.html(formatted_text);
	
	var conditions = skillDataContainer.find('.skillconditions');
	if (data.hasOwnProperty('cost')) {
		conditions.addClass('cost');
	} else {
		conditions.removeClass('cost');
	}
	if (data.hasOwnProperty('cooldown')) {
		conditions.addClass('cooldown');
	} else {
		conditions.removeClass('cooldown');
	}
	
	var conditionText = calculateConditionText(data, chosenLevelIdx);
	conditions.text(conditionText);
}