• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Tried GEMINI for Jass

Level 3
Joined
Jun 17, 2015
Messages
55
I tried with some experiments will JASS triggers, I'm curious with AI and wanted to see how it can turns my ideas into reality.

Gave it an idea to create 2 dummy missles that travel to target point in parabolic, but seems it FAILED TO MOVE and doesn't know how where is wrong.

Here are the trigger & map:
JASS:
//===========================================================================
// Arcane Bolts Spell
// Date: 2024-05-20 (Version 19 - No Debug Messages)
// Author: Gemini AI (with user input)
// Movement: Straight Line X/Y, Parabolic Z
//===========================================================================

//===========================================================================
// Configuration
//===========================================================================
globals
    // --- Spell and Dummy Unit Raw Codes ---
    constant integer SPELL_ID = 'A000'
    constant integer DUMMY_GOLD_BOLT_ID = 'e001'
    constant integer DUMMY_ENERGY_BOLT_ID = 'e000'
    constant integer DUMMY_ABILITY_LOCUST = 'Aloc'
    constant integer ABILITY_CROW_FORM = 'Amrf' // For ensuring flying movement type

    // --- Damage and Mana Burn Scaling ---
    constant real DAMAGE_BASE = 25.0
    constant real DAMAGE_PER_LEVEL = 25.0
    constant real MANA_BURN_BASE = 25.0
    constant real MANA_BURN_PER_LEVEL = 25.0
    constant real AREA_OF_EFFECT = 200.00

    // --- Timings & Movement Parameters ---
    constant real INITIAL_DELAY = 0.50
    constant real TRAVEL_TIME = 1.5   // Increased for visual clarity
    constant real ENERGY_BOLT_SPAWN_DELAY = 0.50
    constant real MOVEMENT_UPDATE_INTERVAL = 0.03

    // --- Z-Arc Calculation ---
    constant real Z_ARC_PEAK_FACTOR = 0.50 // Peak Z height as a factor of total distance
    hashtable BoltData = null

    // --- Hashtable Keys ---
    constant integer HT_KEY_TIMER_CASTER_UNIT = 0
    constant integer HT_KEY_TIMER_TARGET_X = 1
    constant integer HT_KEY_TIMER_TARGET_Y = 2
    constant integer HT_KEY_TIMER_OWNER_PLAYER = 3
    constant integer HT_KEY_MOVE_TIMER_DUMMY_UNIT = 100 // Key for timer -> dummy link
    constant integer HT_KEY_DUMMY_BOLT_TYPE = 200
    constant integer HT_KEY_DUMMY_ORIG_CASTER_UNIT = 201
    constant integer HT_KEY_DUMMY_TARGET_X = 202
    constant integer HT_KEY_DUMMY_TARGET_Y = 203
    constant integer HT_KEY_DUMMY_START_X = 204
    constant integer HT_KEY_DUMMY_START_Y = 205
    constant integer HT_KEY_DUMMY_TIME_ELAPSED = 208
    constant integer HT_KEY_DUMMY_MOVEMENT_TIMER = 209 // Key for dummy -> timer link
    constant integer HT_KEY_DUMMY_TOTAL_DISTANCE = 210

    constant integer BOLT_TYPE_GOLDEN = 1
    constant integer BOLT_TYPE_ENERGY = 2
endglobals

//===========================================================================
// Bolt Logic - Functions ordered for correct JASS parsing
//===========================================================================

function CleanupBoltData takes unit dummyBolt returns nothing
    local integer dummyUnitId
    local timer moveTimer
    local integer moveTimerHandle

    if dummyBolt == null then
        return
    endif
    set dummyUnitId = GetHandleId(dummyBolt)

    if HaveSavedHandle(BoltData, dummyUnitId, HT_KEY_DUMMY_MOVEMENT_TIMER) then
        set moveTimer = LoadTimerHandle(BoltData, dummyUnitId, HT_KEY_DUMMY_MOVEMENT_TIMER)
        if moveTimer != null then
            set moveTimerHandle = GetHandleId(moveTimer)
            call PauseTimer(moveTimer)
            call DestroyTimer(moveTimer)
            call FlushChildHashtable(BoltData, moveTimerHandle)
        endif
    endif
    call FlushChildHashtable(BoltData, dummyUnitId)
    if GetUnitTypeId(dummyBolt) != 0 then
        call RemoveUnit(dummyBolt)
    endif
    set dummyBolt = null
    set moveTimer = null
endfunction

function BoltImpact takes unit dummyBolt returns nothing
    local integer dummyUnitId
    local integer boltType
    local unit originalCaster
    local player originalCasterOwner
    local real targetX
    local real targetY
    local location impactLoc
    local group g
    local unit FoGUnit
    local integer spellLevel
    local real calculatedDamage
    local real calculatedManaBurn

    if dummyBolt == null then
        return
    endif
    set dummyUnitId = GetHandleId(dummyBolt)

    if not HaveSavedInteger(BoltData, dummyUnitId, HT_KEY_DUMMY_BOLT_TYPE) then
        if GetUnitTypeId(dummyBolt) != 0 then
            call RemoveUnit(dummyBolt)
        endif
        return
    endif

    set boltType = LoadInteger(BoltData, dummyUnitId, HT_KEY_DUMMY_BOLT_TYPE)
    set originalCaster = LoadUnitHandle(BoltData, dummyUnitId, HT_KEY_DUMMY_ORIG_CASTER_UNIT)
    set targetX = LoadReal(BoltData, dummyUnitId, HT_KEY_DUMMY_TARGET_X)
    set targetY = LoadReal(BoltData, dummyUnitId, HT_KEY_DUMMY_TARGET_Y)
    set impactLoc = Location(targetX, targetY)
    set g = CreateGroup()

    set spellLevel = 0
    if originalCaster != null and GetUnitTypeId(originalCaster) != 0 then
        set originalCasterOwner = GetOwningPlayer(originalCaster)
        set spellLevel = GetUnitAbilityLevel(originalCaster, SPELL_ID)
    else
        set originalCasterOwner = GetOwningPlayer(dummyBolt)
    endif

    set calculatedDamage = DAMAGE_BASE + (DAMAGE_PER_LEVEL * spellLevel)
    set calculatedManaBurn = MANA_BURN_BASE + (MANA_BURN_PER_LEVEL * spellLevel)

    call GroupEnumUnitsInRangeOfLoc(g, impactLoc, AREA_OF_EFFECT, null)
    loop
        set FoGUnit = FirstOfGroup(g)
        exitwhen FoGUnit == null
        call GroupRemoveUnit(g, FoGUnit)
        if originalCaster != null and GetUnitTypeId(originalCaster) != 0 and IsUnitEnemy(FoGUnit, originalCasterOwner) and GetWidgetLife(FoGUnit) > 0.405 and not IsUnitType(FoGUnit, UNIT_TYPE_STRUCTURE) and not IsUnitType(FoGUnit, UNIT_TYPE_DEAD) then
            if boltType == BOLT_TYPE_GOLDEN then
                call UnitDamageTarget(originalCaster, FoGUnit, calculatedDamage, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
            elseif boltType == BOLT_TYPE_ENERGY then
                call UnitDamageTarget(originalCaster, FoGUnit, calculatedDamage, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
                call SetUnitState(FoGUnit, UNIT_STATE_MANA, GetUnitState(FoGUnit, UNIT_STATE_MANA) - calculatedManaBurn)
            endif
        endif
    endloop

    call RemoveLocation(impactLoc)
    call DestroyGroup(g)
    call CleanupBoltData(dummyBolt)
    set impactLoc = null
    set g = null
    set FoGUnit = null
    set originalCaster = null
    set originalCasterOwner = null
endfunction

function MoveBolt takes nothing returns nothing
    local timer t
    local integer timerHandle
    local unit dummyBolt
    local integer dummyUnitId
    local real timeElapsed
    local real startX
    local real startY
    local real targetX
    local real targetY
    local real currentPROGRESS
    local real newX
    local real newY
    local real newZ
    local real totalDistance
    local real zArcPeakHeight

    set t = GetExpiredTimer()
    set timerHandle = GetHandleId(t)
    set dummyBolt = LoadUnitHandle(BoltData, timerHandle, HT_KEY_MOVE_TIMER_DUMMY_UNIT)

    if dummyBolt == null or GetUnitTypeId(dummyBolt) == 0 then
        call PauseTimer(t)
        call FlushChildHashtable(BoltData, timerHandle)
        call DestroyTimer(t)
        set t = null
        return
    endif

    set dummyUnitId = GetHandleId(dummyBolt)
    if not HaveSavedReal(BoltData, dummyUnitId, HT_KEY_DUMMY_TIME_ELAPSED) then
        call PauseTimer(t)
        call FlushChildHashtable(BoltData, timerHandle)
        call DestroyTimer(t)
        set t = null
        set dummyBolt = null
        return
    endif

    set timeElapsed = LoadReal(BoltData, dummyUnitId, HT_KEY_DUMMY_TIME_ELAPSED) + MOVEMENT_UPDATE_INTERVAL
    set startX = LoadReal(BoltData, dummyUnitId, HT_KEY_DUMMY_START_X)
    set startY = LoadReal(BoltData, dummyUnitId, HT_KEY_DUMMY_START_Y)
    set targetX = LoadReal(BoltData, dummyUnitId, HT_KEY_DUMMY_TARGET_X)
    set targetY = LoadReal(BoltData, dummyUnitId, HT_KEY_DUMMY_TARGET_Y)
    set totalDistance = LoadReal(BoltData, dummyUnitId, HT_KEY_DUMMY_TOTAL_DISTANCE)

    if TRAVEL_TIME <= 0 then
        set currentPROGRESS = 1.0
    else
        set currentPROGRESS = timeElapsed / TRAVEL_TIME
    endif
    if currentPROGRESS > 1.0 then
        set currentPROGRESS = 1.0
    endif

    if currentPROGRESS >= 1.0 then
        set newX = targetX
        set newY = targetY
    else
        set newX = startX + (targetX - startX) * currentPROGRESS
        set newY = startY + (targetY - startY) * currentPROGRESS
    endif
       
    call SetUnitX(dummyBolt, newX)
    call SetUnitY(dummyBolt, newY)
   
    if totalDistance > 0 then
        set zArcPeakHeight = totalDistance * Z_ARC_PEAK_FACTOR
        set newZ = zArcPeakHeight * 4.0 * currentPROGRESS * (1.0 - currentPROGRESS)
        call SetUnitFlyHeight(dummyBolt, newZ, 0)
    else
        call SetUnitFlyHeight(dummyBolt, 0.0, 0)
    endif

    if currentPROGRESS >= 1.0 then
        call BoltImpact(dummyBolt)
    else
        call SaveReal(BoltData, dummyUnitId, HT_KEY_DUMMY_TIME_ELAPSED, timeElapsed)
    endif

    set t = null
    set dummyBolt = null
endfunction

function LaunchBolt takes integer boltType, unit casterUnit, player ownerPlayer, real targX, real targY returns nothing
    local real startX
    local real startY
    local real casterFacing
    local unit dummyBolt
    local integer dummyBoltUnitIdToCreate
    local real dx
    local real dy
    local real dist

    local integer createdDummyUnitId
    local timer moveTimer
    local integer moveTimerHandle

    if casterUnit == null or GetUnitTypeId(casterUnit) == 0 then
        return
    endif
    if ownerPlayer == null then
        set ownerPlayer = GetOwningPlayer(casterUnit)
        if ownerPlayer == null then
            return
        endif
    endif

    set startX = GetUnitX(casterUnit)
    set startY = GetUnitY(casterUnit)
    set casterFacing = GetUnitFacing(casterUnit)

    if boltType == BOLT_TYPE_GOLDEN then
        set dummyBoltUnitIdToCreate = DUMMY_GOLD_BOLT_ID
    else
        set dummyBoltUnitIdToCreate = DUMMY_ENERGY_BOLT_ID
    endif

    set dummyBolt = CreateUnit(ownerPlayer, dummyBoltUnitIdToCreate, startX, startY, casterFacing)
    if dummyBolt == null then
        return
    endif

    call UnitAddAbility(dummyBolt, DUMMY_ABILITY_LOCUST)
    call UnitAddAbility(dummyBolt, ABILITY_CROW_FORM)
    call UnitRemoveAbility(dummyBolt, ABILITY_CROW_FORM)
    call UnitApplyTimedLife(dummyBolt, 'BTLF', TRAVEL_TIME + 2.0)

    set dx = targX - startX
    set dy = targY - startY
    set dist = SquareRoot(dx*dx + dy*dy)
   
    set createdDummyUnitId = GetHandleId(dummyBolt)
    set moveTimer = CreateTimer()
    set moveTimerHandle = GetHandleId(moveTimer)

    call SaveInteger(BoltData, createdDummyUnitId, HT_KEY_DUMMY_BOLT_TYPE, boltType)
    call SaveUnitHandle(BoltData, createdDummyUnitId, HT_KEY_DUMMY_ORIG_CASTER_UNIT, casterUnit)
    call SaveReal(BoltData, createdDummyUnitId, HT_KEY_DUMMY_TARGET_X, targX)
    call SaveReal(BoltData, createdDummyUnitId, HT_KEY_DUMMY_TARGET_Y, targY)
    call SaveReal(BoltData, createdDummyUnitId, HT_KEY_DUMMY_START_X, startX)
    call SaveReal(BoltData, createdDummyUnitId, HT_KEY_DUMMY_START_Y, startY)
    call SaveReal(BoltData, createdDummyUnitId, HT_KEY_DUMMY_TOTAL_DISTANCE, dist)
    call SaveReal(BoltData, createdDummyUnitId, HT_KEY_DUMMY_TIME_ELAPSED, 0.0)
    call SaveTimerHandle(BoltData, createdDummyUnitId, HT_KEY_DUMMY_MOVEMENT_TIMER, moveTimer)
   
    call SaveUnitHandle(BoltData, moveTimerHandle, HT_KEY_MOVE_TIMER_DUMMY_UNIT, dummyBolt)

    call TimerStart(moveTimer, MOVEMENT_UPDATE_INTERVAL, true, function MoveBolt)

    set dummyBolt = null
    set moveTimer = null
endfunction

function HandleDelayed_LaunchEnergyBolt takes nothing returns nothing
    local timer t
    local integer timerHandle
    local unit casterUnit
    local real targetX
    local real targetY
    local player ownerPlayer

    set t = GetExpiredTimer()
    set timerHandle = GetHandleId(t)

    set casterUnit = LoadUnitHandle(BoltData, timerHandle, HT_KEY_TIMER_CASTER_UNIT)
    set targetX = LoadReal(BoltData, timerHandle, HT_KEY_TIMER_TARGET_X)
    set targetY = LoadReal(BoltData, timerHandle, HT_KEY_TIMER_TARGET_Y)
    set ownerPlayer = LoadPlayerHandle(BoltData, timerHandle, HT_KEY_TIMER_OWNER_PLAYER)

    if casterUnit != null and GetUnitTypeId(casterUnit) != 0 and ownerPlayer != null then
        call LaunchBolt(BOLT_TYPE_ENERGY, casterUnit, ownerPlayer, targetX, targetY)
    endif

    call FlushChildHashtable(BoltData, timerHandle)
    call DestroyTimer(t)
    set t = null
    set casterUnit = null
    set ownerPlayer = null
endfunction

function HandleInitialDelay_LaunchGoldenAndScheduleEnergy takes nothing returns nothing
    local timer t
    local integer timerHandle
    local unit casterUnit
    local real targetX
    local real targetY
    local player ownerPlayer
    local timer energyBoltDelayTimer
    local integer energyBoltDelayTimerHandle

    set t = GetExpiredTimer()
    set timerHandle = GetHandleId(t)

    set casterUnit = LoadUnitHandle(BoltData, timerHandle, HT_KEY_TIMER_CASTER_UNIT)
    set targetX = LoadReal(BoltData, timerHandle, HT_KEY_TIMER_TARGET_X)
    set targetY = LoadReal(BoltData, timerHandle, HT_KEY_TIMER_TARGET_Y)
    set ownerPlayer = LoadPlayerHandle(BoltData, timerHandle, HT_KEY_TIMER_OWNER_PLAYER)

    if casterUnit != null and GetUnitTypeId(casterUnit) != 0 and ownerPlayer != null then
        call LaunchBolt(BOLT_TYPE_GOLDEN, casterUnit, ownerPlayer, targetX, targetY)

        set energyBoltDelayTimer = CreateTimer()
        set energyBoltDelayTimerHandle = GetHandleId(energyBoltDelayTimer)
        call SaveUnitHandle(BoltData, energyBoltDelayTimerHandle, HT_KEY_TIMER_CASTER_UNIT, casterUnit)
        call SaveReal(BoltData, energyBoltDelayTimerHandle, HT_KEY_TIMER_TARGET_X, targetX)
        call SaveReal(BoltData, energyBoltDelayTimerHandle, HT_KEY_TIMER_TARGET_Y, targetY)
        call SavePlayerHandle(BoltData, energyBoltDelayTimerHandle, HT_KEY_TIMER_OWNER_PLAYER, ownerPlayer)
        call TimerStart(energyBoltDelayTimer, ENERGY_BOLT_SPAWN_DELAY, false, function HandleDelayed_LaunchEnergyBolt)
        set energyBoltDelayTimer = null
    endif

    call FlushChildHashtable(BoltData, timerHandle)
    call DestroyTimer(t)
    set t = null
    set casterUnit = null
    set ownerPlayer = null
endfunction

//===========================================================================
// Main Trigger Setup
//===========================================================================
function ArcaneBolts_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == SPELL_ID
endfunction

function ArcaneBolts_Actions takes nothing returns nothing
    local unit caster
    local player casterOwner
    local location targetLoc
    local real targetX
    local real targetY
    local timer initialDelayTimer
    local integer initialDelayTimerHandle

    if BoltData == null then
        // If you ever need to debug hashtable initialization, uncomment the next line
        // call BJDebugMsg("ArcaneBolts_Actions ERROR: BoltData hashtable is NULL!")
        return
    endif

    set caster = GetTriggerUnit()
    if caster == null then
        return
    endif
    set casterOwner = GetOwningPlayer(caster)
    set targetLoc = GetSpellTargetLoc()
    if targetLoc == null then
        return
    endif

    set targetX = GetLocationX(targetLoc)
    set targetY = GetLocationY(targetLoc)
    call RemoveLocation(targetLoc)
    set targetLoc = null         

    set initialDelayTimer = CreateTimer()
    set initialDelayTimerHandle = GetHandleId(initialDelayTimer)

    call SaveUnitHandle(BoltData, initialDelayTimerHandle, HT_KEY_TIMER_CASTER_UNIT, caster)
    call SaveReal(BoltData, initialDelayTimerHandle, HT_KEY_TIMER_TARGET_X, targetX)
    call SaveReal(BoltData, initialDelayTimerHandle, HT_KEY_TIMER_TARGET_Y, targetY)
    call SavePlayerHandle(BoltData, initialDelayTimerHandle, HT_KEY_TIMER_OWNER_PLAYER, casterOwner)
    call TimerStart(initialDelayTimer, INITIAL_DELAY, false, function HandleInitialDelay_LaunchGoldenAndScheduleEnergy)

    set caster = null
    set casterOwner = null
    set initialDelayTimer = null
endfunction

//===========================================================================
// Initialization
//===========================================================================
function InitTrig_ArcaneBolts takes nothing returns nothing
    local trigger t = CreateTrigger()

    if BoltData == null then
        set BoltData = InitHashtable()
    endif

    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function ArcaneBolts_Conditions))
    call TriggerAddAction(t, function ArcaneBolts_Actions)
    set t = null
endfunction
 

Attachments

  • WorldEditTestMap.w3x
    26.2 KB · Views: 2
Last edited:
While I'm at work right now (cannot open map), a quick look at code and it seems to be ok code. Not great, but ok.
It does projectiles the old-school-way with units instead of special effects. It works, but is waaay worse with regards of performance.

Anyways, what happens or don't happen? What "fails"?
Do you have units and spells with the correct IDs?
 
I took a look at the map and it seems like you have the dummy's movement speed set to 0 in the object editor (for both energy bolt and light bolt). You'll want to set it to some positive value (e.g. "1", or reset it to its default value). Otherwise SetUnitX/SetUnitY will technically update the coordinates, but the unit's model will remain stuck in the same position.

But +1 to what ThompZon said, there are newer natives that allow you to do the same things with effects directly and that's a lot more convenient. But I'm not sure if Gemini is aware of all the new natives. You could also try having it use Lua, as the resulting code for something like this is waaaay simpler since you can capture variables inside closures (so you probably wouldn't need to use hashtables at all).
 
Level 3
Joined
Jun 17, 2015
Messages
55
While I'm at work right now (cannot open map), a quick look at code and it seems to be ok code. Not great, but ok.
It does projectiles the old-school-way with units instead of special effects. It works, but is waaay worse with regards of performance.

Anyways, what happens or don't happen? What "fails"?
Do you have units and spells with the correct IDs?
The result is dummy units just stay at caster's position, meanwhile everything is ok..
 
Top