Skip to content

SCData Explained: An Easy Way to Modify SC Data With Code

Dr-zzt edited this page Sep 1, 2024 · 1 revision

As the initial contributor of the feature https://github.com/armoha/eudplib/pull/26, I am glad to see it finally integrated into the main branch.

Feature Summary

The TrgUnit, Weapon, Flingy, Sprite, Image, Upgrade, Tech, and UnitOrder classes have been changed and now it is possible to change StarCraft data through their members.

The TrgPlayer class has also been changed to support modifying some of the player-related data.

// Make Marine max health 60
TrgUnit("Terran Marine").maxHp = 60 * 256;
// Give Ghost Shield
TrgUnit("Terran Ghost").hasShield = true;
// Increase Vulture Attack by 3
TrgUnit("Terran Vulture").groundWeapon.damage += 3;

// Make Zergling Draw Functionality into Hallucination
Image("Zergling").drawingFunction = 16;

// Show Player 7 Gas
printAll("\x04P7 가스: {}", P7.gas);

P1.terranSupplyMax = 800; // Make max Terran supply to 400

These classes support cast and can be used conveniently in typed EUD functions or EUD structs.

function make_unit_stronger(unitID: TrgUnit) {
    unitID.maxHp += 20;
    unitID.maxShield += 20;
}

make_unit_stronger("Protoss High Templar");

Additionally, CUnit and CSprite have been changed so that the members that previously gave unit numbers, player numbers, etc. now return the corresponding classes.

// hero is a CUnit

hero.unitType.maxHp += 20 * 256;
hero.owner.ore += 100;

Motivation

(Most of this copied from https://github.com/armoha/eudplib/pull/26)

Previously it's surprisingly difficult to manipulate SC data via code(eps/plib) without the help of EUD Editor. For example, suppose we want to increase Fragmentation Grenade's damage by 3.

We have to find the address (0x656EB0) and size (2) of the weapon damage amount offset, and need to write in the offset. (We need a reference such as EUD Book)

const offset = 0x656EB0 + EncodeWeapon("Fragmentation Grenade") * 2;
wwrite(offset, wread(offset) + 3);

In this code, the only thing that makes sense (in terms of understanding the code by reading) is the EncodeWeapon part. What is 0x656EB0 supposed to do? Why * 2? Is this code increasing fragmentation grenade's range, or splash inner radius, or damage? While eps-server provides some information for offsets, this is far from being a comfortable coding experience.

Some mapmakers have built their own libraries to solve this issue - for example, one example could be Westreed's DataEdit.

import DataEdit as de;

const frag_grenade = de.DWeapon(EncodeWeapon("Fragmentation Grenade"));
frag_grenade.SetDamageAmount(frag_grenade.GetDamageAmount() + 3);

Now we know what the code is doing, but it's still a bit verbose. What if the Vulture weapon is not fixed to Fragmentation Grenade, but can be changed? What if we need to increase the damage of the current weapon by 3?

import DataEdit as de;

const vulture_weapon = de.DUnit($U("Terran Vulture")).GetGroundWeapon();
const frag_grenade = de.DWeapon(vulture_weapon);
frag_grenade.SetDamageAmount(frag_grenade.GetDamageAmount() + 3);

Does it need to be this complex?

There is also the issue of the efficiency of the implementation. In the case of DataEdit, it's written entirely in eps, making it extremely inefficient. However, a Python library would also result in a lot of unnecessary triggers without a significant amount of optimization. Therefore, I thought it's important to embed this functionality within eudplib to solve the problems.

TrgUnit("Terran Vulture").groundWeapon.damage += 3;

The same thing neatly reduced into a single line.

How to use

The TrgUnit, Weapon, Flingy, Sprite, Image, Upgrade, Tech, UnitOrder, and TrgPlayer classes take a number, variable, or string as a constructor argument (the same form as the EncodeXXX).

const vulture_data = TrgUnit("Terran Vulture");

Each class has a set of members. One can access/change SC data by reading/writing to those members. The byte size will be automatically managed. Remember that HP needs to be multiplied by 256; (unlike in the case of CUnit, shields should NOT be multiplied by 256).

vulture_data.maxHp = 100 * 256;

If the corresponding data is of a specific type, it returns a class of that type. However, reading/writing a number also works.

const weapon_data = vulture_data.groundWeapon; // weapon_data is of Weapon type
weapon_data.damage += 3;

vulture_data.groundWeapon = 0; // changes to weapon ID 0
printAll("{}", vulture_data.groundWeapon + 10); // 10 will be printed

For players, all of the constants like P1, P2, ... have been updated to TrgPlayer. However, use only P1 to P12, as using things like Force1 and Foes in the same way will not work correctly.

P1.ore += 70;

As mentioned above, all these classes support casting and can be used in eps functions or objects easily.

function make_unit_stronger(unitID: TrgUnit) {
    unitID.maxHp += 20 * 256;
    unitID.maxShield += 20;
}

make_unit_stronger("Protoss High Templar");
object HeroData {
    var unitID: TrgUnit;

    function make_me_stronger() {
        this.unitID.maxHp += 20 * 256;
        this.unitID.maxShield += 20;
    }
};

However, it is strongly advised to not directly use .cast for normal use. Due to the additional optimizations for automatic casting as shown above, casting directly can lead to unexpected results. I recommend calling the constructor directly instead of casting.

var a = 0;
const u = TrgUnit.cast(a);
a++;
printAll("{}", u); // This will print 1, not 0


var b = 0;
const u2 = TrgUnit(b);
b++;
printAll("{}", u2); // Prints 0 as expected

List of members by class

The = means that they are referring to the same thing. (Two names in one data)

Comma doesn't mean anything; it's just grouping similar things together.

TrgUnit

graphic = flingy (Flingy Type) subUnit (TrgUnit Type) constructionGraphic startDirection hasShield maxShield maxHp elevation movementFlags rank computerIdleOrder, humanIdleOrder, returnToIdleOrder, attackUnitOrder, attackMoveOrder (UnitOrder Type) groundWeapon, airWeapon (TrgUnit Type) maxGroundHits, maxAirHits AIFlags baseProperty seekRange, sightRange armorUpgrade sizeType armor rightClickAction readySound, whatSoundStart, whatSoundEnd, pissedSoundStart, pissedSoundEnd, yesSoundStart, yesSoundEnd buildingDimensions (Position Type) portrait mineralCost, gasCost timeCost requirementOffset groupFlags supplyProvided, supplyUsed transportSpaceProvided, transportSpaceRequired buildScore, killScore nameString broodWarFlag, stareditAvailabilityFlags

Weapon

label graphic = flingy (Flingy Type) targetFlags minRange maxRange upgrade = upgradeID damageType behavior removeAfter explosionType splashInnerRadius, splashMiddleRadius, splashOuterRadius damage damageBonus cooldown damageFactor

Sprite

image = imageID (Image Type) isVisible

Image

isTurnable = graphicTurn isClickable useFullIscript drawIfCloaked drawingFunction iscript = iscriptID

Upgrade

mineralCostBase mineralCostFactor gasCostBase gasCostFactor timeCostBase timeCostFactor requirementOffset icon label race maxLevel broodWarFlag

Tech

mineralCost gasCost timeCost energyCost requirementOffset icon label race researched broodWarFlag UnitOrder label useWeaponTargeting canBeInterrupted canBeQueued canBeObstructed weapon = weaponID techUsed animation buttonIcon requirementOffset obscuredOrder

TrgPlayer

mineral = ore gas cumulativeGas cumulativeMineral = cumulativeOre zergControlAvailable, zergControlUsed, zergControlMax terranSupplyAvailable, terranSupplyUsed, terranSupplyMax protossPsiAvailable, protossPsiUsed, protossPsiMax

Clone this wiki locally