BGforgeNet / VScode-BGforge-MLS

BGforge MultiLanguage server
https://forums.bgforge.net/viewforum.php?f=35
Other
16 stars 5 forks source link

[WeiDU, feature request]Add support for SSL/SLB files #20

Closed 4Luke4 closed 4 years ago

4Luke4 commented 4 years ago

@burner1024

Since we're speaking of BAF files, I must confess that I rarely use them – I prefer to code scripts in SSL, it's much more compact and versatile.

So, my question is: what do you think about adding support for this format?

burner1024 commented 4 years ago

Yes, I had that in mind since the beginning. It's just that no one asked, and since there's no demand - there's no supply. I didn't use SSL myself, so I'm going to need formal syntax description. Full, with all the words. And some samples. And I don't know what SLB is.

Also, there's a particular issue of nerds being really creative with names:

I mean, seriously? Although, MultiLanguage Server = MLS :roll_eyes: who am I to complain. Will need to add an extra setting or something to distinguish.

4Luke4 commented 4 years ago

I didn't use SSL myself, so I'm going to need formal syntax description. Full, with all the words. And some samples. And I don't know what SLB is.

Well, you could start by looking at the underlying Perl source code here. The ssl files are the actual code, whereas the slb files are "library files" containing common data. I can give you lots of code snippets but first you'd better get familiar with its syntax (shouldn't be too hard for you....). Also, the thread linked above contains some introductory examples you might want to look at...

burner1024 commented 4 years ago

I don't see how perl code would help. I think it's going to be a fool's errand to work on implementing it without having any first hand experience, sorry. So if syntax tree isn't described by someone who works with SSL often, it'll have to wait until I actually use it somewhere.

4Luke4 commented 4 years ago

I don't see how perl code would help. I think it's going to be a fool's errand to work on implementing it without having any first hand experience, sorry. So if syntax tree isn't described by someone who works with SSL often, it'll have to wait until I actually use it somewhere.

I see. If that's the case, then I'll write down the related context-free grammar with the help of some code samples... I've been using it for quite some time, so I'd like to try...

burner1024 commented 4 years ago

That'd be great. The important thing is to define standard blocks, actual code words don't matter so much.

4Luke4 commented 4 years ago

@burner1024

Since any BAF block is valid in SSL (but not in SLB!), you could start by implementing that....

burner1024 commented 4 years ago

No point, starting from more specific blocks might lead to throwing it all away later. Need to start from the root.

4Luke4 commented 4 years ago

I see.

So, just to give you an idea (SSL format):

BEGIN_ACTION_DEFINITION
    Name(AttackReevaluate)
    TRIGGER
        Allegiance(Myself,GOODCUTOFF)
    ACTION
        RESPONSE #scsprob1
            AttackReevaluate(scstarget,scsargument1)
END

IF TRIGGER
    TargetBlock(EVILCUTOFFsInOrder)
    TriggerBlock(MainhandWeaponUsable)
    StateCheck(scstarget,0x9)
    !StateCheck(scstarget,STATE_REALLY_DEAD)
    Range(scstarget,4)
THEN DO
    Action(AttackReevaluate,45)
END

TargetBlocks and TriggerBlocks are defined in a separate SLB file:

TARGET=EVILCUTOFFsInOrder
    [EVILCUTOFF]
    SecondNearest([EVILCUTOFF])
    ThirdNearest([EVILCUTOFF])
    FourthNearest([EVILCUTOFF])
    FifthNearest([EVILCUTOFF])
    SixthNearest([EVILCUTOFF])

TRIGGER=MainhandWeaponUsable
    WeaponEffectiveVs(scstarget,MAINHAND)
    WeaponCanDamage(scstarget,MAINHAND)

So, to sum up, that ssl block will compile as:

IF
    Allegiance(Myself,GOODCUTOFF)
    See([EVILCUTOFF])
    WeaponEffectiveVs([EVILCUTOFF],MAINHAND)
    WeaponCanDamage([EVILCUTOFF],MAINHAND)
    StateCheck([EVILCUTOFF],0x9) // 0x9 will compile as 'STATE_SLEEPING | STATE_STUNNED'
    !StateCheck([EVILCUTOFF],STATE_REALLY_DEAD)
    Range([EVILCUTOFF],4)
THEN
         RESPONSE #100
            AttackReevaluate([EVILCUTOFF],45)
END

// ........
// Up to:

IF
    Allegiance(Myself,GOODCUTOFF)
    See(SixthNearest([EVILCUTOFF]))
    WeaponEffectiveVs(SixthNearest([EVILCUTOFF]),MAINHAND)
    WeaponCanDamage(SixthNearest([EVILCUTOFF]),MAINHAND)
    StateCheck(SixthNearest([EVILCUTOFF]),0x9) // 0x9 will compile as 'STATE_SLEEPING | STATE_STUNNED'
    !StateCheck(SixthNearest([EVILCUTOFF]),STATE_REALLY_DEAD)
    Range(SixthNearest([EVILCUTOFF]),4)
THEN
         RESPONSE #100
            AttackReevaluate(SixthNearest([EVILCUTOFF]),45)
END

As you can see, that's pretty powerful...

burner1024 commented 4 years ago

I see. For the purposes of highlighting doesn't really matter how it compiles, though. SSL blocks looks simple enough. The problem with SLB is that I don't see what marker to detect as the end of the block (both TARGET and TRIGGER)

4Luke4 commented 4 years ago

I see. For the purposes of highlighting doesn't really matter how it compiles, though.

Yes, you're right, I specified it for clarification purposes...

The problem with SLB is that I don't see what marker to detect as the end of the block (both TARGET and TRIGGER)

As far as I know, there's no marker to detect the end of both a TARGET block and a TRIGGER block...

4Luke4 commented 4 years ago

Next: on top of standard BAF operators (i.e., OR(), !, @), you need to add &, |, and ||:

TriggerBlock(IsTargetableWithSpells|MR50|SINecromancy|MinorGlobe)
Action(Spell,CLERIC_CURSE|100|50)
IF TRIGGER
    ConditionalTargetBlock(EVILCUTOFFsInOrderShort;OR(5)&Class(scstarget,MAGE_ALL)&Class(scstarget,CLERIC_ALL)&Class(scstarget,DRUID_ALL)&Class(scstarget,BARD_ALL)&Class(scstarget,SHAMAN))
    TargetBlock(EVILCUTOFFsInOrder)
    TriggerBlock(MainhandWeaponUsable)
    !StateCheck(scstarget,STATE_REALLY_DEAD)
THEN DO
    Action(AttackOneRound)
END
IF TRIGGER
    HaveSpell(CLERIC_ZONE_OF_SWEET_AIR)
    Global("Cloud","LOCALS",0)
    BEGIN LOOP(scsloopvar||;Second;Third;Fourth;Fifth;Sixth)
        OR(7)
            SpellCast(scsloopvarNearestEnemyOf(Myself),WIZARD_INCENDIARY_CLOUD)
            SpellCast(scsloopvarNearestEnemyOf(Myself),WIZARD_STINKING_CLOUD)
            SpellCast(scsloopvarNearestEnemyOf(Myself),WIZARD_CLOUDKILL)
            SpellCast(scsloopvarNearestEnemyOf(Myself),WIZARD_DEATH_FOG)
            SpellCast(scsloopvarNearestEnemyOf(Myself),WIZARD_WRITHING_FOG)
            SpellCast(scsloopvarNearestEnemyOf(Myself),WIZARD_SUFFOCATE)
            SpellCast(scsloopvarNearestEnemyOf(Myself),CLERIC_CLOUD_OF_PESTILENCE)
    END LOOP
THEN DO
    SetGlobal("Cloud","LOCALS",1)
    SetGlobalTimer("CLTM","LOCALS",TEN_ROUNDS)
    Continue()
END
4Luke4 commented 4 years ago

Additional SSL keywords:

INCLUDE FILE("%path_to_ssl_file%")

Combine() // this one takes no argument...

IgnoreBlock("%name_of_slb_block%") // defined in a separate SLB file

RequireBlock("%name_of_slb_block%") // defined in a separate SLB file

ConditionalTargetBlock("%name_of_target_block%";"%list_of_baf_triggers%")

ActionCondition("%action_name%","%list_of_arguments%") // According to the underlying Perl code, this is identical to standard 'Action()'s – probably it's not been implemented yet...

BEGIN LOOP("%variable_name%"||var1;var2;...) ... END LOOP

VARIABLE()

SSLBoolean(firstBoolean|secondBoolean|etc)

Code snippets:

IF TRIGGER
     IgnoreBlock(IsCleric)
     RequireBlock(HasL3)
     TargetBlock(PCsPreferringWeak)
     TriggerBlock(Disabled|MR|ResistElectric|MinorGlobe|Enemy|SIAlteration)
     AreaType(OUTDOOR)
     IgnoreBlock(Demivrgvs)
THEN DO
     Action(Spell,CLERIC_CALL_LIGHTNING|70|30)
END
VARIABLE(cat=dog)
// ..........
SetGlobal("cat","LOCALS",1) // will be replaced by 'SetGlobal("dog","LOCALS",1)'

INCLUDE FILE() and Combine():

INCLUDE FILE(mod_folder/lib/ssl/attack.ssl)
INCLUDE FILE(mod_folder/lib/ssl/spellcasting.ssl)

IF TRIGGER
    ConditionalTargetBlock(Protagonist;Allegiance(Myself,EVILCUTOFF))
    ConditionalTargetBlock(PCsInOrderShort;Allegiance(Myself,EVILCUTOFF))
    TargetBlock(EnemiesInOrder)
    TriggerBlock(IsTargetableWithSpells|MR50|SIEnchantment|MinorGlobe)
    TriggerBlock(ImmuneToParalysis)
    General(scstarget,HUMANOID)
    !StateCheck(scstarget,STATE_IMMOBILE)
    !StateCheck(scstarget,STATE_REALLY_DEAD)
THEN DO
    Combine()
    Action(Spell,CLERIC_HOLD_PERSON|100|50)
END
IF
  !GlobalTimerNotExpired("castspell","LOCALS")
  !GlobalTimerNotExpired("belhifet_jump","LOCALS")
  Global("belhifet_jump_loc","GLOBAL",0)
THEN
    BEGIN LOOP(scsloopvar||1;2;3;4;5;6)
    RESPONSE #100
        SetGlobal("belhifet_jump_loc","GLOBAL",scsloopvar)
        SetGlobalTimer("castspell","LOCALS",6)
        SetGlobalTimer("belhifet_jump_timer","LOCALS",24)
        ForceSpellPoint(%belhifet_loc_scsloopvar%,INNATE_INFERNAL_CONVEYANCE)
        Wait(4)
        FaceObject([PC])
    END LOOP
END

Nested LOOPs:

BEGIN LOOP(!StateCheck\(scstarget,STATE_NOT_TARGETABLE\)|| )
BEGIN LOOP(!StateCheck\(scstarget,STATE_INVISIBLE\) || )
    INCLUDE FILE(%scsroot%/caster_shared/caster_definitions.ssl)
END LOOP
END LOOP

SSLBoolean():

BEGIN_ACTION_DEFINITION
    Name(Spell)
    TRIGGER
        SSLBoolean(scsargument1)
        HaveSpell(scsargument1)
    ACTION
        RESPONSE #scsprob1
            Spell(scstarget,scsargument1)
END

IF TRIGGER
    Target(NearestEnemyOf(Myself))
THEN DO
    Action(Spell,WIZARD_MAGIC_MISSILE)
    Action(Spell,WIZARD_CHROMATIC_ORB)
END

An additional example of BEGIN_ACTION_DEFINITION:

BEGIN_ACTION_DEFINITION
    Name(Spell)
    TRIGGER
        !GlobalTimerNotExpired("cast_spell","LOCALS")
        HaveSpell(scsargument1)
        Allegiance(Myself,EVILCUTOFF)
        !StateCheck(Myself,STATE_SILENCED)
        CheckStatLT(Myself,scsargument2,scsargument3)
        !Allegiance(scstarget,EVILCUTOFF)
    ACTION
        RESPONSE #scsprob1
            SetGlobalTimer("cast_spell","LOCALS",ONE_ROUND)
            Spell(scstarget,scsargument1)
END
4Luke4 commented 4 years ago

SLB format: there are four main keywords: TRIGGER, TRIGGER_REPLACE, TARGET, and TARGET_REPLACE. Basically, each TRIGGER block is a collection of BAF triggers, whereas each TARGET block is a collection of script objects. The additional object scstarget is a valid script object in TRIGGER blocks.

TRIGGER=IsSpellcaster
    OR(5)
        Class(scstarget,MAGE_ALL)
        Class(scstarget,CLERIC_ALL)
        Class(scstarget,DRUID_ALL)
        Class(scstarget,BARD_ALL)
        Class(scstarget,SHAMAN)

TRIGGER=Dummy
    CheckStatLT(scstarget,100,RESISTMISSILE)
        PartyRested()
    OR(2)
        CheckStatLT(scstarget,75,RESISTMISSILE)
        GlobalTimerNotExpired("targetcompromise","LOCALS")

TRIGGER=AoEIceStorm
        OR(3)
           NextTriggerObject(scstarget)Range(NearestAllyOf(Myself),15)
           NumInParty(1)
           GlobalTimerNotExpired("targetcompromise","LOCALS")
        OR(4)
           NextTriggerObject(scstarget)!Range(NearestEnemyOfType([0.HUMANOID]),22)
           NextTriggerObject(scstarget)CheckStatGT(NearestEnemyOfType([0.HUMANOID]),59,RESISTCOLD)
           NextTriggerObject(scstarget)CheckStatGT(NearestEnemyOfType([0.HUMANOID]),59,RESISTMAGIC)
           NextTriggerObject(scstarget)Race(NearestEnemyOfType([0.HUMANOID]),RAKSHASA)

TARGET=Beholders
    [0.0.BEHOLDER]
    SecondNearest([0.0.BEHOLDER])
    ThirdNearest([0.0.BEHOLDER])
    FourthNearest([0.0.BEHOLDER])
    FifthNearest([0.0.BEHOLDER])

TARGET=EnemySpellcasters
    [EVILCUTOFF.0.0.MAGE_ALL]
    SecondNearest([EVILCUTOFF.0.0.MAGE_ALL])
    ThirdNearest([EVILCUTOFF.0.0.MAGE_ALL])
    [EVILCUTOFF.0.0.BARD]
    [EVILCUTOFF.0.0.CLERIC_ALL]
    [EVILCUTOFF.0.0.DRUID_ALL]
    SecondNearest([EVILCUTOFF.0.0.CLERIC_ALL])
    SecondNearest([EVILCUTOFF.0.0.DRUID_ALL])

TARGET=EnemiesInReverseOrder
    SixthNearestEnemyOf(Myself)
    FifthNearestEnemyOf(Myself)
    FourthNearestEnemyOf(Myself)
    ThirdNearestEnemyOf(Myself)
    SecondNearestEnemyOf(Myself)
    NearestEnemyOf(Myself)
burner1024 commented 4 years ago

I added basic hightlighting for both types, you should be able to try them in development mode. I know it's not perfect yet, but the bulk is there. You might need to set files associations to have .ssl detected as WeiDU variant:

"files.associations": {
  "*.ssl": "weidu-ssl"
}

Feel free to try and report any issues, as usual, with screenshots. Well, you can try to handle them yourself, too. Syntax definition is basically a bunch of regexes including each other, and if you know WeiDU, you should be familiar with regexes as well. (After fixing yml, run scripts/syntaxes_to_json.sh).

4Luke4 commented 4 years ago

@burner1024

Looks fine, good job!

There's a color conflict with the keyword KIT: as you know, this is both a script trigger and a STAT. The problem is that it gets highlighted as a STAT when used as a script trigger...

KIT

Also, this issue affects standard BAF files as well...

burner1024 commented 4 years ago

Without delving too deep, we could make it case sensitive and resolve the conflict that way.

4Luke4 commented 4 years ago

Without delving too deep, we could make it case sensitive and resolve the conflict that way.

Yes, that sounds more robust.

In so doing, you'll be able to distinguish, say, the TriggerBlock MinorGlobe from the STAT MINORGLOBE... Or, in other words, every TriggerBlock will be highlighted with same color...

TriggerBlock
burner1024 commented 4 years ago

I don't see stat MINORGLOBE. Maybe it's better to use case sensitive names for everything. The reason they were added as case insensitive is because the compiler doesn't care. I don't mind the extension making a gentle push to better code style, though. Anyway, such a change is trivial, so I'll leave it to whoever wants to do that.

4Luke4 commented 4 years ago

I don't see stat MINORGLOBE.

It's STAT#64.

4Luke4 commented 4 years ago

I don't see stat MINORGLOBE.

It's STAT#64.

Look here.

burner1024 commented 4 years ago

Right, it's the other way around, it's in the stat, but not in triggers.

burner1024 commented 4 years ago

OK then, if there isn't any obvious issue, I guess this may be closed.