• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Detecting if external file exists

Status
Not open for further replies.

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
What would be an efficient way to find out if an external, readable file exists at WC3 runtime?

So basicly, what I want to do is the following:
If a user has Local File support enabled and some music placed inside a certain folder; the game detects that on map init and sets a player flag which makes the map load different sound files for the music played in the map.

Playing the sound files is local code anyway, so there shouldn't be any chance for desyncs. All I need is a "file exists" check from inside the map.


I mean, the following should work, right?

JASS:
local string default = "Music\\Doom.mp3"
local string custom = "MyFolder\\CustomMusic.mp3"
local string path = ""
local sound s
if GetSoundFileDuration(custom) > 0 then
    set path = custom
else
    set path = default
endif
set s = CreateSound(path ,false, false, false, 10, 10, "")
call StartSound(s)


This way, a user would just have to enable local files and drop external music files into the specified folder in the WC3 directory. If a user doesn't have local files enabled or doesn't have the music files, it will just play the default track instead.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
That's possible. But I prefer to use a single boolean for the whole game.

JASS:
// s is local sound
set s = CreateSound(random sound file from local folder, false, false, false, 10, 10, "")
set LocalFilesAvailable = GetSoundFileDuration(s) > 0
call KillSound(s)
// str is local string
if LocalFilesAvailable then
    set str = a sound from local folder
else
    set str = an imported sound
endif
// This way it won't desync
call PlaySound(str)
As you can see LocalFilesAvailable will be a local variable, everything under that condition will run locally so be careful.

But I haven't found a way how to check availability of a model or image files in the local folder tho (it would be cool if possible).
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
But I haven't found a way how to check availability of a model or image files in the local folder tho (it would be cool if possible).
The uses for that would be limited anyway, as not many jass functions actually take models or images as input.

Actually, special effects or trackables are the only functions I know that create objects with model input at runtime.
Image files would only apply to ... images, I suppose. So also limited functionality.

Having external music or sounds is probably the only real case where this really matters, except maybe for having an external set of attachment models applied via special effects.


I can't think of an idea to detect local special effects either... but you can always ship your extension folder with a dummy sound to get the duration from.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Confirmed working. GetSoundFileDuration(path) can be used to detect external sound files.

Awesome for external music libraries or external voice acting files for your multiplayer projects!

Of course it's working. Cz it's used in Glideon too. :) It would be better if image files can be checked as well somehow.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Of course it's working. Cz it's used in Glideon too. :) It would be better if image files can be checked as well somehow.
Doesn't really matter, imho. You can always have a small dummy audio file in your external libraries to check if the library exists or not.
If the user deliberately deletes files from your external libraries, it's his fault after all if it ain't working anymore.
 
Yeah GetSoundFileDuration() works quite well. And I think it is the best way to detect an external mpq.

Theoretically, you could do some crazy stuff such as upgrading unit models/unit textures. It requires a lot of clever trickery and a specific implementation, but I remember reading on wc3jass a long time ago that you could create a unit with one ID for one client and another ID for another client. For example, let's say you have a regular footman, hfoo, and the upgraded hfo1. It would look something like this:
JASS:
local integer id = 'hfoo'
if externalmpq then
    set id = 'hfo1'
endif
set footman = CreateUnit(Player(0), id, 0, 0, 0)
Of course, externalmpq would be a boolean set as to whether the client had the sound file of your external mpq. If I recall correctly, the post said that if the only difference between the two objects is the unit model, then it won't desync. But I would need to test it.

It would probably require a lot of refactoring if implemented into an existing map though, considering that unit-type ID's would be out of sync. The best way to counteract it would be to write a custom unit-type ID function so that they return the same ID across all clients. i.e. you could make a table of the "upgraded" objects, and then have them return the id of their original object:
JASS:
function GetUnitTypeIdEx takes unit u returns integer
    local integer id = GetUnitTypeId(u)
    if upgraded.has(id) then
        return upgraded[id] // upgraded[id] maps to "unupgraded" version
    endif
    return id
endfunction

Something along those lines. Sorry if this sounds convoluted. It probably would be more trouble than it is worth, but I think it is an interesting concept. But idk, perhaps wc3 would desync after all.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Yeah GetSoundFileDuration() works quite well. And I think it is the best way to detect an external mpq.

Theoretically, you could do some crazy stuff such as upgrading unit models/unit textures. It requires a lot of clever trickery and a specific implementation, but I remember reading on wc3jass a long time ago that you could create a unit with one ID for one client and another ID for another client. For example, let's say you have a regular footman, hfoo, and the upgraded hfo1. It would look something like this:
JASS:
local integer id = 'hfoo'
if externalmpq then
    set id = 'hfo1'
endif
set footman = CreateUnit(Player(0), id, 0, 0, 0)
Of course, externalmpq would be a boolean set as to whether the client had the sound file of your external mpq. If I recall correctly, the post said that if the only difference between the two objects is the unit model, then it won't desync. But I would need to test it.

It would probably require a lot of refactoring if implemented into an existing map though, considering that unit-type ID's would be out of sync. The best way to counteract it would be to write a custom unit-type ID function so that they return the same ID across all clients. i.e. you could make a table of the "upgraded" objects, and then have them return the id of their original object:
JASS:
function GetUnitTypeIdEx takes unit u returns integer
    local integer id = GetUnitTypeId(u)
    if upgraded.has(id) then
        return upgraded[id] // upgraded[id] maps to "unupgraded" version
    endif
    return id
endfunction

Something along those lines. Sorry if this sounds convoluted. It probably would be more trouble than it is worth, but I think it is an interesting concept. But idk, perhaps wc3 would desync after all.
So a different ID doesn't neccesarily desync then? But I think this approach is way too dangerous to use in any serious multiplayer map. Just too many potential bugs that could screw up the game.

However, I could see how this could be used for instance in combination with the Warclub ability to change textures.
Basicly have two identical destructables except for the texture. Then create them on different IDs for local players and immediately eat them with the warclub ability and remove them again. This would basicly leave no room for TypeID clashes, as the destructables pretty much get removed again the moment they get used.

Could allow for having lowres and a highres unit texture set for imported heroes/units.
 
So a different ID doesn't neccesarily desync then? But I think this approach is way too dangerous to use in any serious multiplayer map. Just too many potential bugs that could screw up the game.

I haven't tested it. It was just a snippet I read a long time ago, so I assume it works. And yeah, it is probably easier to design your map around it rather than implement it after things have been already made.
 
Level 12
Joined
Feb 22, 2010
Messages
1,115
I tested it long time ago, different unit types with only model difference won't cause desync by itself, if you are careful.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
well, as per rule of logic, if the unit is completly the same but with different model, there should be no problem, because animations are ran per client, not synced on server, and everything else does not change the game state of any given client(move speed the same, rotation the same, attack speed the same, health regen the same, you could go on and on for days)
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
well, as per rule of logic, if the unit is completly the same but with different model, there should be no problem, because animations are ran per client, not synced on server, and everything else does not change the game state of any given client(move speed the same, rotation the same, attack speed the same, health regen the same, you could go on and on for days)
Yeah I can see what you mean; but still... the differences in UnitTypeIds are a real boner-kill for this approach, as you basicly use them almost everywhere.
And rewriting GetUnitTypeId with some (relatively) large extra overhead?

But the idea combined with war club is interesting, as it gets rid of the TypeId problem (or rather, moves the typeId problem to dummy destructables that you don't care about) for units and allows you to externalize the textures, which means you can basicly ship a "high res pack" with your map which will update all unit textures to use 512x512 maps.


Also, something WAY more simple and obvious: you can update all imported doodads and destructables to have 2 animations: one that uses the default texture and one that uses an uncompressed highres texture.
Then just use "Play Animation For Doodads in Region" and BAM, all doodads have 512x512 uncompressed blps!

Especially interesting for highres doodad packs, like B²M which you can only use in multiplayer maps by pretty much reducing the texture quality ad absurdity. Just ship an external texture pack and add the animation to the model files.

I have no experience with the "Play Animation for Doodads in Region" function; I could imagine it sucking some performance when all doodads have animations, but I might be wrong. But I'll give it a try today by modifying the B²M house models and see if it works.


... now we only need a way to update ground tiles ingame for higher resolution variants... that would be the ultimate goal for any highres pack.
 
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Preloader, GetLocationZ, CreateImage returns -1 for an invalid path.
Nice find!

I actually tested the SetDoodadAnimationRect() method now and confirmed working.
I changed the farmhouse from B2m and added a "morph" animation that creates a second material layer over the existing material layers, using the external filepath. Then used a dummy .wav to test if local files are enabled and changed the animation ingame.

Left side of the picture: before dropping the external textures in the WC3 folder
Right side: after dropping the external textures in the WC3 folder.


... now the tedious part is changing ALL the models to have a morph animation using the external filepath...
 

Attachments

  • 1.jpg
    1.jpg
    297.3 KB · Views: 786

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Applied this method to all house and tree models now. But I think about updating the shrubs and props aswell... it's a ton of work, though, as I need to create a special animation for all those models.

Updating almost 3000 doodads with over 200 rawcodes via SetDoodadAnimationRect creates a barely noticable FPS spike. But as you basicly only have to do this once per game, who really cares?

I got to say this is pretty neat...
 

Attachments

  • 01_off.jpg
    01_off.jpg
    332.3 KB · Views: 171
  • 01_on.jpg
    01_on.jpg
    364.1 KB · Views: 129
  • 02_off.jpg
    02_off.jpg
    224.7 KB · Views: 136
  • 02_on.jpg
    02_on.jpg
    251 KB · Views: 129
  • 03_off.jpg
    03_off.jpg
    462.6 KB · Views: 117
  • 03_on.jpg
    03_on.jpg
    460.1 KB · Views: 169

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
GetLocationZ because solid models usually have a height in the center. You can use the model in a walkable destructable and check if GetLocationZ changed where you set the destructable.
I see; but that would require all destructables to be walkable, though.

But it doesn't really matter. We don't really need one way of checking per media type, since basicly, what people will want to do is shipping everything as a zipped folder to be dumped into the WC3 folder and be done with it. If people intentionally delete files from the pack, then that's not really my problem.

Made this a reality now and did all the model and code adjustments for it. It's pretty neat, especially as it works completely behind the scenes and has no chance for desyncs. We should spread awareness of this!


It basicly also allows having doodads with ingame textures, then swap them out for highres custom textures if people installed the external folder. Endless possibilities in multiplayer map terraining.
The only downside is that I still wouldn't want a map to look ugly when a user doesn't have the pack installed. So you still have to build your map around it, eventually.


Has anyone experimented with large scale terrain texture replacements based on images? Does it have a huge performance impact?

Since you can get GetTerrainType and GetTerrainVariance, you can basicly replace every single tile and tile variation with an external texture on a per-cell base, including the corner tiles.
You'd basicly need 4x4 textures for a small tile and 8x4 textures for every large tile.
You could then use the same algorithm that I used on my Destructable Hider lib, in the way that it replaces all tiles currently visible by the game camera with 1-cell-images.
I could imagine this causing a large performance hit, but then again, I could be wrong, because you can hash it pretty well and it doesn't do anything when not moving the camera much (just as destructable hider).

This would basicly allow up to a resolution of 512x512 pixels PER cell, compared to 32x32 resolution we currently have.

Is there a flaw in my thinking here? Are image handle IDs limitated in the same way as texttags?
 
Level 8
Joined
Jul 24, 2006
Messages
157
Has anyone experimented with large scale terrain texture replacements based on images? Does it have a huge performance impact?
You can use .tga files as tilesets, they allow a much larger size than blps with their size limit. I tested it with a tileset that has variations and is 2048*1024 large, so a single tile has a 256*256 texture.
Besides If I remeber correctly wc3 first searches for the tga and after that for the blp. In your case you could try to change the Terrain.slk for the imported tileset texture paths so replacing them with the tga via local files would only affect your map.

it's a ton of work, though, as I need to create a special animation for all those models.
You could write a script with my Java MDX Library http://www.hiveworkshop.com/forums/warcraft-editing-tools-277/java-mdx-lib-257069/.
(Or when you can give me a doodad befor and after you added the animation I will try)
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
You can use .tga files as tilesets, they allow a much larger size than blps with their size limit. I tested it with a tileset that has variations and is 2048*1024 large, so a single tile has a 256*256 texture.
Besides If I remeber correctly wc3 first searches for the tga and after that for the blp. In your case you could try to change the Terrain.slk for the imported tileset texture paths so replacing them with the tga via local files would only affect your map.


You could write a script with my Java MDX Library http://www.hiveworkshop.com/forums/warcraft-editing-tools-277/java-mdx-lib-257069/.
(Or when you can give me a doodad befor and after you added the animation I will try)

I have no experience working with the Terrain.slk. I should maybe check it out before going nuts on this image approach (though it is already working in my proof of concept).
The problem with any such replacements is that you need to be able to change them on runtime as you can not force players to use an external database. Which kind of eliminates the idea to modify the tile paths directly...

except if you modify the path to a tile that is currently not used in the map; then use SetTerrainType to change to that unused tile locally. I don't know the limits of SetTerrainType, though. Gotta check that out.
Looks like this will be a busy evening. So much stuff to try out...
 
Status
Not open for further replies.
Top