Staredit Network > Forums > Modding Assistance > Topic: Eisetley's C++ and iscript questions festival.
Eisetley's C++ and iscript questions festival.
Dec 5 2015, 11:51 am
By: Eisetley  

Dec 5 2015, 11:51 am Eisetley Post #1



I decided to start this thread because my mod is going to have a lot of GPTP related stuff and by looking at my code you can probably tell I don't really have much experience with this. I don't want to start a new thread for every issue, so basically welcome to my private blog.
A few words about the mod: the working title is Hi-Tech-Toss. I'm going to bring some of the finest SC2 toss tech to BW, then create a storyline and campaign focused on the Protoss race.

WARP GATES



I almost succeeded with making these, the evil thing is I'll have to sacrifice one spell for every unit produced at gateway (5 spells including sentry) but it's ok since it's all about making Protoss cooler.

1. Pylon field requirement for unit placement.
This is something I can not figure out, but I think it's possible because protoss buildings require pylon field = there's a function that checks if certain part of terrain is pylon-powered. This would also allow me to kill zealots if psi provider is killed during the warp. Any tips?

2. Showing all psi fields when clicking on a Warp Gate.
So close yet so far. I did it by editing psi_field.cpp this way:
Quote

bool isReadyToMakePsiField(CUnit *unit) {
//Default StarCraft behavior

if (unit->id == UnitId::pylon
||unit->UnitId::Special_WarpGate)
return true;

return false;
}
it crashes the game after a while until I added Warp Gate to canMakePsiField conditions. But then Warp Gates started to emit psi field which is the thing I don't want.

3. Passable terrain check.
When I try to warp zealot on the water, nothing happens, so it's ok. But when I target some kind of an edge or any other unpasssable terrain, you can hear a sound for "Unit Unplaceable" error and the game crashes. Look at the end of the video I posted.

4. Iscript animation - warp overlay.
At the moment when zealot is warping in, I use the unused warp flash animation, which basically makes zealot glow white. It would be cooler if it was warp overlay instead of warp flash, but there's not much documentation about warpoverlay opcode, so I have no idea how to use it.

SOURCE
weapon_fire.cpp
Quote
//////*****Zealot Warp-In*****\\\\\\


//not enough minerals
if ((unit->id == UnitId::Special_WarpGate)
&& (unit->mainOrderId == OrderId::Ensnare)
&& (resources->minerals[unit->playerId]) < (units_dat::MineralCost[UnitId::zealot]))

{
scbw::showErrorMessageWithSfx(unit->playerId, 850, 149);

}




//construct additional pylons
if ((unit->id == UnitId::Special_WarpGate)
&& (unit->mainOrderId == OrderId::Ensnare)
&& (scbw::getSupplyRemaining(unit->playerId, RaceId::Protoss)) < (units_dat::SupplyRequired[UnitId::zealot]))


{
scbw::showErrorMessageWithSfx(unit->playerId, 846, 155);

}



//proceed with warp

if ((unit->id == UnitId::Special_WarpGate)
&& (unit->mainOrderId == OrderId::Ensnare)
&& (scbw::getSupplyRemaining(unit->playerId, RaceId::Protoss)) >= (units_dat::SupplyRequired[UnitId::zealot])
&& (resources->minerals[unit->playerId]) >= (units_dat::MineralCost[UnitId::zealot]))
{


CUnit *warpedZealot;
u32 WarpZealotX = unit->orderTarget.pt.x;
u32 WarpZealotY = unit->orderTarget.pt.y;
unit->playIscriptAnim(IscriptAnimation::IsWorking);
warpedZealot = scbw::createUnitAtPos(UnitId::zealot, unit->playerId, WarpZealotX, WarpZealotY);
warpedZealot->playIscriptAnim(IscriptAnimation::SpecialState1);
warpedZealot->hitPoints = 5120;
warpedZealot->shields = 5120;
warpedZealot->currentButtonSet = 202;
warpedZealot->stimTimer = 5;
warpedZealot->mainOrderId = OrderId::Stop;
resources->minerals[unit->playerId] = resources->minerals[unit->playerId] - units_dat::MineralCost[UnitId::zealot];
}

game_hooks.cpp inside NextFrame() ofc.

Quote
//////**********ZEALOT WARP-IN LOOPS**********\\\\\\\\\\\\\\\\\\\


CUnit *warpzealot;
for (int zealot_warp_loop = 0; zealot_warp_loop < 1700; zealot_warp_loop++)
{
warpzealot = &unitTable[zealot_warp_loop];
if ((warpzealot->id == UnitId::zealot)
&& (warpzealot->stimTimer > 0)
&& (warpzealot->hitPoints) < (units_dat::MaxHitPoints[warpzealot->id]))
{
warpzealot->setHp(warpzealot->hitPoints + 512);
warpzealot->mainOrderId = OrderId::Stop;
}

if ((warpzealot->id == UnitId::zealot)
&& (warpzealot->stimTimer > 0)
&& (warpzealot->shields) < (units_dat::MaxHitPoints[warpzealot->id]))
{
warpzealot->shields = (warpzealot->shields + 256);
warpzealot->mainOrderId = OrderId::Stop;
}

if ((warpzealot->id == UnitId::zealot)
&& (warpzealot->stimTimer == 0))
{
//scbw::playSound(666, nullptr);
warpzealot->currentButtonSet = 65;
//warpzealot->orderToIdle();


}




None.

Dec 5 2015, 12:16 pm Neiv Post #2



Quote from Eisetley
4. Iscript animation - warp overlay.
At the moment when zealot is warping in, I use the unused warp flash animation, which basically makes zealot glow white. It would be cooler if it was warp overlay instead of warp flash, but there's not much documentation about warpoverlay opcode, so I have no idea how to use it.

For the warp texture, one possibility is to use an image which has its drawfunc set to 12 (Warp Flash (SEdit crash) in Datedit) in images.dat,after which you imgol that image. The image's iscript can then contain warpoverlay commands, which choose the frame of the warp texture. Also, you'll have to figure out a way to remove the overlay after unit has warped in.

Alternatively, you could try coding it like bw does. Bw temporarily overrides unit's iscript with the iscript of warp texture, sets image's drawfunc to 0xc, and restores them back once unit is finished. No idea how you would do that cleanly though.



None.

Dec 5 2015, 12:24 pm Eisetley Post #3



So it would be something like
followmaingraphics
warpoverlay 0
wait 1
warpoverlay 1
wait 1
warpoverlay 2
and so on?



None.

Dec 5 2015, 12:31 pm Neiv Post #4



Yes, that should work. And the image's .grp needs to be the zealot grp, not the warp texture one.



None.

Dec 5 2015, 10:34 pm Eisetley Post #5



Issue 4 fixed, thanks Neiv.

I added CanBeObstructed flag to Parasite order and now it doesn't crash the game but if I try to warp zealot on water or an edge of an island far far away, zealot gets warped near the warp gate. Important discovery: changing "Energy" of an order in datedit changes the targeting too, especially if use weapon targeting flag is not used.



None.

Dec 8 2015, 12:35 pm Eisetley Post #6



SENTRY'S GUARDIAN SHIELD

I tried to make this by looking at the cloaking nearby units code, but it doesn't work as intended. There's just one problem: the only unit affected by damage reduction and getting the overlay is the Sentry that casted Guardian Shield. Halp :hurr:.



None.

Dec 8 2015, 4:52 pm UndeadStar Post #7



I'm not sure what a "Guardian Shield" is supposed to do, but assuming it's to reduce damage as it is inflicted, maybe the code should be in weapon_damage.cpp (from memory, maybe it's elsewhere), and when damage is inflicted, it look for a nearby sentry with that shield activated, and if found in range, decrease the damage?



None.

Dec 8 2015, 11:10 pm Eisetley Post #8



Already fixed, forgot to attach my weapon_damage.cpp to the previous post. The problem now is that for some reason it doesn't give a shit about attacker's weapon range so I had to use distance calculation between the attacker and the target, and it sucks because if an unit has ranged weapon and is close to the target, the damage is not reduced.
Quote
bool unitIsUnderGuardianShield;
const u8 damageType = weapons_dat::DamageType[weaponId];
u32 GSRadius = 105;
scbw::UnitFinder ShieldedSentriesInRange(
target->getX() - GSRadius, target->getY() - GSRadius,
target->getX() + GSRadius, target->getY() + GSRadius);

ShieldedSentriesInRange.forEach([damage, target, GSRadius, attacker, damageType, &unitIsUnderGuardianShield](CUnit* sentry)
{

if ((sentry->getDistanceToTarget(target) <= GSRadius
&& sentry->id == UnitId::gui_montag
&& sentry->unusedTimer != 0
&& sentry->playerId == target->playerId))

{

{

unitIsUnderGuardianShield = true;
}

}
}
);
if ((unitIsUnderGuardianShield)
&& (damageType != DamageType::IgnoreArmor)
&& (attacker->getDistanceToTarget(target) > 24))



{
damage = damage - 512;
target->sprite->createOverlay(139);
}
If I replaced (attacker->getDistanceToTarget(target) > 24) with something like attacker->getMaxWeaponRange(attacker->getAirWeapon()) >= 3 || attacker->getMaxWeaponRange(attacker->getGroundWeapon()) >= 3, it doesn't reduce damage or place an overlay.
also
Quote
I'm not sure what a "Guardian Shield" is supposed to do
Basically this: http://wiki.teamliquid.net/starcraft2/Guardian_Shield

Attachments:
weapon_damage.cpp
Hits: 1 Size: 6.85kb

Post has been edited 1 time(s), last time on Dec 8 2015, 11:23 pm by Eisetley.



None.

Dec 9 2015, 9:28 am Neiv Post #9



You are trying to prevent the effect from activating at melee attack, right?

Quote from Eisetley
If I replaced (attacker->getDistanceToTarget(target) > 24) with something like attacker->getMaxWeaponRange(attacker->getAirWeapon()) >= 3 || attacker->getMaxWeaponRange(attacker->getGroundWeapon()) >= 3, it doesn't reduce damage or place an overlay.
also

Check that the attacker actually has a air/ground weapon before calling respective getMaxWeaponRange. Calling it with a "none" weapon will cause unpredictable results. Additionally, if the attacker is tank/goliath, you may need to use some additional code to access the turret and get its weapon.

--

Completely unrelated to your questions, but using bexpl/bfire palettes with sentry spell graphics would make them prettier imo.



None.

Dec 9 2015, 10:20 am Eisetley Post #10



Quote from Neiv
Completely unrelated to your questions, but using bexpl/bfire palettes with sentry spell graphics would make them prettier imo.
Definitely, no idea how to do it though. Is there any special palette that will recolor image into these fancy, remapable colors?



None.

Dec 9 2015, 2:01 pm Neiv Post #11



PyMS comes with palettes that you can use.

Also there's http://www.staredit.net/topic/8505/ which can be used for generating palette files from the remap .pcxs.



None.

Dec 9 2015, 11:30 pm Eisetley Post #12



Tried this
Quote

if ((unitIsUnderGuardianShield)
&& (damageType != DamageType::IgnoreArmor))
{
if (attacker->getAirWeapon() != WeaponId::None && attacker->getMaxWeaponRange(attacker->getAirWeapon()) >= 3
|| attacker->getGroundWeapon() != WeaponId::None && attacker->getMaxWeaponRange(attacker->getGroundWeapon()) >= 3)
{
damage = damage - 512;
target->sprite->createOverlay(139);
}
}
and this
Quote
if ((unitIsUnderGuardianShield)
&& (damageType != DamageType::IgnoreArmor))
{
if (attacker->getGroundWeapon() != WeaponId::None
|| attacker->getAirWeapon() != WeaponId::None)
{
if (attacker->getMaxWeaponRange(attacker->getAirWeapon()) >= 3
|| attacker->getMaxWeaponRange(attacker->getGroundWeapon()) >= 3)
{
damage = damage - 512;
target->sprite->createOverlay(139);
}
}
Still ignores weapon range and reduces all damage. Edit: and crashes the game too :massimo:

Post has been edited 1 time(s), last time on Dec 10 2015, 12:48 am by Eisetley.



None.

Dec 10 2015, 10:10 am UndeadStar Post #13



For damage calculation, maybe you should use "damage = damage - std::min(damage, 512)" so it doesn't become negative.
You don't need getgroundweapon() and getairweapon() in weaponDamageHook() of weapon_damage.cpp, because the id of the weapon currently inflicting damage is currently stored in weaponId.
For the range, maybe you should use weapons_dat::MaxRange[weaponId] instead.It doesn't take in account range bonus, but if the objective is to tell melee apart from ranged, it should work, and doesn't rely on attacker variable, that may be null (in case of splash damage or unit killed while the projectile is on its way)



None.

Dec 12 2015, 8:56 pm Eisetley Post #14



I cannibalized ensnareTimer, because bool produced weird and buggy results like placing overlay and reducing damage on random units, including enemy units. Now it works okay, but weapon range is still ignored for some reason. Maybe I should use weapons_dat::MaxRange[weaponId] > 2*32 instead of weapons_dat::MaxRange[weaponId] > 2 or something?

Quote

u32 GSRadius = 105;
scbw::UnitFinder ShieldedSentriesInRange(
target->getX() - GSRadius, target->getY() - GSRadius,
target->getX() + GSRadius, target->getY() + GSRadius);

ShieldedSentriesInRange.forEach([target, GSRadius](CUnit* sentry)
{

if ((sentry->getDistanceToTarget(target) <= GSRadius
&& sentry->id == UnitId::gui_montag
&& sentry->unusedTimer != 0
&& sentry->playerId == target->playerId))

{

{

target->ensnareTimer = 1;
}

}


}
);

if (target->ensnareTimer > 0
&& weapons_dat::MaxRange[weaponId] > 2
&& damageType != DamageType::IgnoreArmor
&& (!(units_dat::BaseProperty[target->id] & UnitProperty::Building)))

{
damage = damage - std::min(damage, 512);
target->sprite->createOverlay(139);
}
Edit: okay, weapons_dat::MaxRange[weaponId] > 2*32 worked, guardian shield finally works as I want it to.

Post has been edited 1 time(s), last time on Dec 12 2015, 9:16 pm by Eisetley.



None.

Dec 13 2015, 4:49 pm SCRuler Post #15



So is this a plugin or what?
Also are you gonna make it available for us?



None.

Dec 13 2015, 6:26 pm Eisetley Post #16



Quote from SCRuler
So is this a plugin or what?
Also are you gonna make it available for us?
Yes, Firegraft plugins written using GPTP https://github.com/SCMapsAndMods/general-plugin-template-project/wiki. I'll release it when there will be enough content to make at least a tech demo or something. I'm not sure if releasing source would be a good idea, because I suck at C++ and I don't want people to repeat my mistakes. If someone would really want it I wouldn't mind sending sources via email.



None.

Jan 25 2016, 8:10 pm Eisetley Post #17



Okay, finally got some time to work on the mod again, I also lacked decent internet connection for a few weeks. Done a lot of stuff. Now it's possible to warp in all units at Warp Gate (pylon fields are shown when WG is selected too), maelstrom is changed into confusion (a spell that dark archons in LoTV have, forces units to attack allies). Also added an aesthetical feature, which is basically creating additional overlays when units get damaged by a certain weapon - burning when hit by a siege tank, energy sparks when hit by psi storm, etc.
I tried to make forcefields, almost got them down right. Almost.

To make forcefields placeable near each other or on other units I had to do two things. First, they're set to flyers in units.dat. Second, they have gathering status, so they act collision-wise like gathering workers.
Quote
if ((unit->id == UnitId::gui_montag)
&& (unit->mainOrderId == OrderId::StasisField)
&& (scbw::getActiveTileAt(unit->orderTarget.pt.x, unit->orderTarget.pt.y).isWalkable)
&& (!scbw::getActiveTileAt(unit->orderTarget.pt.x, unit->orderTarget.pt.y).isUnwalkable))
{
CUnit *Forcefield;
Forcefield = scbw::createUnitAtPos(UnitId::cargo_ship, 11, unit->orderTarget.pt.x, unit->orderTarget.pt.y);
Forcefield->status = UnitStatus::IsGathering;
Forcefield->removeTimer = 50;
}
The problem is that every time I mess with UnitStatus, unit can't receive orders. When I remove
Quote
Forcefield->status = UnitStatus::IsGathering;
everything works fine, forcefield gets removed when removeTimer reaches 0. When IsGathering status is set, it can't receive the "Die" order and lasts forever.
Second issue is that once I set forcefield to flyer in unit's dat, when I try to place a forcefield under an air unit, it gets placed near it. Scanner Sweep is a flyer too, yet you can place it on air units.
Third issue - forcefields are selectable. I set it to unselectable anywhere I could (units.dat, firegraft), yet you can still select it sometimes.
Okay, let's move on to Time Warp (http://wiki.teamliquid.net/starcraft2/Time_Warp). It causes the same problems as forcefield: doesn't get removed, and is selectable by dragging a box. The code required for it to work is:
update_status_effects.cpp
Quote
u32 TimeWarpRadius = 60;
scbw::UnitFinder EnemyTimeWarpInRange(
unit->getX() - TimeWarpRadius, unit->getY() - TimeWarpRadius,
unit->getX() + TimeWarpRadius, unit->getY() + TimeWarpRadius);

EnemyTimeWarpInRange.forEach([unit, TimeWarpRadius](CUnit* timewarp)
{

if ((timewarp->id == UnitId::UnusedTerran1
&& (timewarp->getDistanceToTarget(unit) <= TimeWarpRadius)))
// && timewarp->isTargetEnemy(unit))

{

{
unit->acidSporeTime[9] = 2;

}

}


}
);
unit_speed.cpp
Quote
//Contains hooks that control unit movement speed, acceleration, and turn speed.

#include "unit_speed.h"
#include "../SCBW/enumerations.h"
#include "../SCBW/scbwdata.h"
#include <SCBW/UnitFinder.h>
namespace hooks {

/// Calculates the unit's modified movement speed, factoring in upgrades and status effects.
///
/// @return The modified speed value.
u32 getModifiedUnitSpeedHook(const CUnit* unit, u32 baseSpeed) {
//Default StarCraft behavior
u32 speed = baseSpeed;
int speedModifier = (unit->stimTimer ? 1 : 0) - (unit->acidSporeTime[9] ? 1 : 0)
+ (unit->status & UnitStatus::SpeedUpgrade ? 1 : 0);
if (speedModifier > 0) {
if (unit->id == UnitId::scout || unit->id == UnitId::Hero_Artanis)
speed = 1707;
else {
speed += speed >> 1;
if (speed < 853)
speed = 853;
}
}
else if (speedModifier < 0)
speed >>= 1;
return speed;

}

/// Calculates the unit's acceleration, factoring in upgrades and status effects.
///
/// @return The modified acceleration value.
u32 getModifiedUnitAccelerationHook(const CUnit* unit) {
//Default StarCraft behavior
u32 acceleration = flingy_dat::Acceleration[units_dat::Graphic[unit->id]];
int modifier = (unit->stimTimer ? 1 : 0) - (unit->acidSporeTime[9] ? 1 : 0)
+ (unit->status & UnitStatus::SpeedUpgrade ? 1 : 0);
if (modifier > 0)
acceleration <<= 1;
else if (modifier < 0)
acceleration -= acceleration >> 2;
return acceleration;
}

/// Calculates the unit's turn speed, factoring in upgrades and status effects.
///
/// @return The modified turning speed value.
u32 getModifiedUnitTurnSpeedHook(const CUnit* unit) {
//Default StarCraft behavior
u32 turnSpeed = flingy_dat::TurnSpeed[units_dat::Graphic[unit->id]];
int modifier = (unit->stimTimer ? 1 : 0) - (unit->acidSporeTime[9] ? 1 : 0)
+ (unit->status & UnitStatus::SpeedUpgrade ? 1 : 0);
if (modifier > 0)
turnSpeed <<= 1;
else if (modifier < 0)
turnSpeed -= turnSpeed >> 2;
return turnSpeed;
}

} //hooks
I already consumed ensnareTimer for something (delay for dragoon's blink I think) but I thought that it'd be a wise idea to make units inside a time warp act like ensnared. I just replaced ensnare timer with acid spore count everywhere in unit_speed. Air units ignore the speed reduction completely (which is good, because it's supposed to work like this, although I don't know why it doesn't affect them) and most of ground units are affected. For some reasons high templars, archons and dark archons go through time warps like nothing happened, keeping their original speed. Any ideas how to fix this?
Last question is about speeding up buildings when it comes to unit training\upgrading\researching.
I tried this (at this point just wanted to make Nexus train probes faster):
Quote
//Loop through all visible units in the game.
for (CUnit *unit = *firstVisibleUnit; unit; unit = unit->link.next) {
//Write your code here
//CHRONO BOOST
if (unit->id == UnitId::ProtossNexus)
{
unit->remainingBuildTime = std::min<u16>(unit->remainingBuildTime, 16);
}
It doesn't work :( .

Post has been edited 1 time(s), last time on Jan 26 2016, 9:28 am by Eisetley.



None.

Jan 28 2016, 7:35 pm KYSXD Post #18



To set any status try using:

Quote
Forcefield->status |= UnitStatus::IsGathering;

To clean:

Quote
Forcefield->status &= ~(UnitStatus::IsGathering);

Using "=" only would clean any but the current flag.

The problem with placing on units seems to be related to the collision.
scbw::createUnitAtPos checks for collision.

For the time warp problem i maybe can recreate the behavior. Let me check. The problem seems to be related to the iscript/flingy flag.

For the chrono boost problem you may check for units produced (do not forget to check if(unit->currentBuildUnit!=NULL) ):

Quote
unit->currentBuildUnit->remainingBuildTime

And for research & upgrades:

Quote
unit->building.upgradeResearchTime





Jan 29 2016, 8:59 am Neiv Post #19



Quote from Eisetley
Third issue - forcefields are selectable. I set it to unselectable anywhere I could (units.dat, firegraft), yet you can still select it sometimes.

I think you'll have to modify the function at 0046ED80, which determines whether an unit is unselectable.

This is only a client side/local player check, and a hacker can bypass that one. If that is a problem, you'll also have to fix functions at 004C2750 (Selection command) and 004C2560 (Add to selection command). That generally isn't problem, but that's what allows nuke anywhere hack to work in vanilla bw, as the nuke can be selected and then ordered to move around.



None.

Jan 29 2016, 2:18 pm Eisetley Post #20



Okay, fixed some stuff, forcefields and time warps now get removed. I'll probably have to write a block of code that will push away any unit that's standing on forcefield's future location. But then if it's not a flyer, I can't place it on ramps.
I'm also trying to make reavers and carriers build scarabs/interceptors automatically.
Quote
//Auto building scarabs and interceptors
if (unit->id == UnitId::reaver || unit->id == UnitId::warbringer)
{
if (unit->carrier.inHangarCount < 5)
{
unit->buildQueue[unit->buildQueueSlot] = UnitId::scarab;
unit->setSecondaryOrder(OrderId::TrainFighter);
}
}
This works correctly until a reaver has 5 scarabs at once. Then it shoots one out, scarab is shown in the queue but the progress bar stands still.
@Neiv
That would require some reverse engineering, right? I'm not really familiar with this.



None.

Options
  Back to forum
Please log in to reply to this topic or to report it.
Members in this topic: None.
[03:58 pm]
lil-Inferno -- u
[02:50 pm]
MTiger156 -- Valve still can't count to 3 :wob:
[01:03 pm]
Voyager7456 -- not really tho
[07:29 am]
Oh_Man -- Half life 3 fcking confirmed mates
[07:24 am]
NudeRaider -- takes like half a second for me. Got 5,8MB/s down
[01:20 am]
MTiger156 -- I got 3MB/s download... but it took a minute >_<
[10:26 pm]
Suicidal Insanity -- Its only 5MB
[10:13 pm]
MTiger156 -- Jeez, I can't even load it LOL my pathetic internet.
[09:13 pm]
Suicidal Insanity -- MTiger156
MTiger156 shouted: Not sure why it takes an ultra-high resolution image of a bird to get that point across, but ok
Ultra high? That is downscaled by a factor of 5 or so
Please log in to shout.


Members Online: ditpeytabra31, Roy, izowit, GGmano, sugar26