Advertisement
Guest User

Untitled

a guest
Sep 12th, 2018
268
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.70 KB | None | 0 0
  1. --[[cannon-smoke]]
  2. --Allows a unit to use a fixed cannon.
  3. --author zaporozhets
  4. --based on scripts by Roses, expwnent and Fleeting Frames
  5. --thanks and credits for researching completion_timer and many other great suggestions, bugfixes and improvements to Grimlocke
  6. --CANNON to the right of them! CANNON to the left of them!
  7.  
  8. local eventful = require 'plugins.eventful'
  9. eventful.enableEvent(eventful.eventType.JOB_INITIATED, 0)
  10. eventful.enableEvent(eventful.eventType.JOB_COMPLETED, 0)
  11.  
  12. local maxrange = 200 --maximum range of cannon (in tiles)
  13. local minrange = 5 --minimum range of cannon (in tiles)
  14. local max_hitrate = 100 --TODO: account for firer siege operation skill (skill number 48) skill levels are 0-20(+? (normalize in case)). dfhack.units.getEffectiveSkill(firer, 48)
  15. local velocity = 20000 --cannonball velocity
  16. local rotationMult = 0.2 --cannon rotation speed multiplier, lower is faster
  17. local loadMult = 0.5 --cannon load speed multiplier
  18.  
  19. gasSpread = 20 --distance (in tiles) gas will attempt to spread output_items
  20. gasDensity = 5000 --amount of gas produced by gas cannisters
  21.  
  22. eventful.onProjItemCheckMovement.cannon = function(projectile)
  23.     if projectile.distance_flown == 1 then
  24.         cannonSmoke(projectile, 5, 10)
  25.     elseif projectile.distance_flown == 2 then
  26.         cannonSmoke(projectile, 5, 25)
  27.     elseif projectile.distance_flown <= 7 then
  28.         cannonSmoke(projectile, 5,  25)
  29.     end
  30. end
  31.  
  32. eventful.onProjItemCheckImpact.cannon = function(projectile)
  33.     if projectile.item:getSubtype() ~= -1 and dfhack.items.getSubtypeDef(projectile.item:getType(),projectile.item:getSubtype()).id == "ITEM_TRAPCOMP_EXPLOSIVESHELL" then
  34.         cannonSmoke(projectile, 6, 50)
  35.     elseif projectile.item:getSubtype() ~= -1 and dfhack.items.getSubtypeDef(projectile.item:getType(),projectile.item:getSubtype()).id == "ITEM_TRAPCOMP_GASSHELL" then
  36.         cannonSmoke(projectile, 10, 5000)
  37.     end
  38. end
  39.  
  40. eventful.onJobInitiated.cannon = function(job)
  41.     if string.find(job.reaction_name, "GUN_") then
  42.         modifyJob(job)
  43.     end
  44. end
  45.  
  46. function modifyJob(job) --modifies job once it's started, otherwise waits for job start and checks again
  47.     if #job.general_refs > 1 then
  48.         local firer = df.unit.find(job.general_refs[1].unit_id)
  49.         makeFearless(firer, true) --only cowards flee
  50.     end
  51.     if job.completion_timer ~= -1 then
  52.         if job.reaction_name == "GUN_ROTATE_CANNON_R" or job.reaction_name == "GUN_ROTATE_CANNON_L" then
  53.             job.completion_timer = math.floor(job.completion_timer * rotationMult) --0.2
  54.         elseif job.reaction_name == "GUN_LOAD_CANNON" then
  55.             job.completion_timer = math.floor(job.completion_timer * loadMult) --0.5
  56.         elseif job.reaction_name == "GUN_FIRE_CANNON" or job.reaction_name == "GUN_TEST_FIRE_CANNON" then
  57.             local firer = df.unit.find(job.general_refs[1].unit_id)
  58.             local cannon = df.building.find(job.general_refs[0].building_id)
  59.             local loadedItems = cannonIsLoaded(firer, cannon)
  60.             if loadedItems then
  61.                 waitForTarget(job, firer, cannon, loadedItems)
  62.             else
  63.                 job.completion_timer = 0 --ya dun goofed
  64.             end
  65.         end
  66.     else
  67.         dfhack.timeout(50, "ticks", function() modifyJob(job) end)
  68.     end
  69. end
  70.  
  71. function makeFearless(firer, mode)
  72.     if mode == true then
  73.         if firer.flags2.vision_missing == false then
  74.             firer.flags2.vision_good = false
  75.             firer.flags2.vision_missing = true
  76.             dfhack.timeout(1000, "ticks", function() makeFearless(firer, false) end)
  77.         end
  78.     else
  79.         firer.flags2.vision_good = true
  80.         firer.flags2.vision_missing = false
  81.     end
  82. end
  83.  
  84. function cannonIsLoaded(firer, cannon)
  85.     local ammo
  86.     local powder
  87.     for _, cannonItem in ipairs(cannon.contained_items) do
  88.         if cannonItem.use_mode == 0 then
  89.             local item = cannonItem.item
  90.             if tostring(item._type) == "<type: item_trapcompst>" and item.flags.forbid == true then
  91.                 ammo = item
  92.             elseif tostring(item._type) == "<type: item_barst>" and item.mat_type == 8 and item.flags.forbid == true then
  93.                 powder = item
  94.             end
  95.         end
  96.     end
  97.     if ammo == nil then
  98.         dfhack.gui.showAnnouncement(firer.name.first_name.." cancels fire cannon: No ammo loaded.", 12)
  99.         if powder ~= nil then powder.flags.forbid = false end
  100.         return false
  101.     elseif powder == nil then
  102.         dfhack.gui.showAnnouncement(firer.name.first_name.." cancels fire cannon: No potash loaded.", 12)
  103.         if ammo ~= nil then ammo.flags.forbid = false end
  104.         return false
  105.     else
  106.         return {ammo, powder}
  107.     end
  108. end
  109.  
  110. function waitForTarget(job, firer, cannon, loadedItems)
  111.     local nearestUnit = false
  112.     local nearestDistance =  768
  113.     for _,target in ipairs(df.global.world.units.active) do
  114.         if checkDirection(cannon, target, 1) then
  115.             local targetDistance = findDistance(firer.pos.x, target.pos.x)
  116.             targetDistance = targetDistance + findDistance(firer.pos.y, target.pos.y)
  117.             targetDistance = targetDistance + findDistance(firer.pos.z, target.pos.z) --TODO: add weighting to z distance if people report excessive bird shooting
  118.             if targetDistance < nearestDistance and targetDistance > minrange then
  119.                 if isHostile(firer, target, job.reaction_name) then
  120.                     if canPathTo(firer.pos, target.pos, targetDistance) then
  121.                         nearestUnit = target
  122.                         nearestDistance = targetDistance
  123.                     end
  124.                 end
  125.             end
  126.         end
  127.     end
  128.     if nearestUnit then
  129.         --printTargetDebug(firer, nearestUnit, nearestDistance)
  130.         fireCannon(firer, cannon, loadedItems, nearestUnit)
  131.         job.completion_timer = 0
  132.     else
  133.         if firer.job.current_job ~= nil and firer.job.current_job.id == job.id then
  134.             job.completion_timer = 100
  135.             dfhack.timeout(50, "ticks", function() waitForTarget(job, firer, cannon, loadedItems) end)
  136.         else
  137.             job.completion_timer = 0
  138.         end
  139.     end
  140. end
  141.  
  142. function fireCannon(firer, cannon, loadedItems, nearestUnit)
  143.     local ammo = loadedItems[1]
  144.     local powder = loadedItems[2]
  145.     local ammoClone = df.item.find(dfhack.items.createItem(ammo:getType(), ammo:getSubtype(), ammo.mat_type, ammo.mat_index, firer))
  146.     if ammo.subtype.id == "ITEM_TRAPCOMP_GASSHELL" then
  147.         ammoClone.improvements:insert("#", ammo.improvements[0])
  148.     end
  149.     ammoClone.flags.forbid = true
  150.     ammoClone.quality = ammo.quality
  151.     ammo.flags.garbage_collect = true
  152.     powder.flags.garbage_collect = true
  153.     local muzzlePos = checkDirection(cannon, firer, 2)
  154.     dfhack.items.moveToGround(ammoClone, muzzlePos)
  155.     fireProjectile(ammoClone, muzzlePos, nearestUnit)
  156. end
  157.  
  158. eventful.onJobCompleted.cannon = function(job)
  159.     if job.reaction_name == "GUN_ROTATE_CANNON_R" or job.reaction_name == "GUN_ROTATE_CANNON_L" then
  160.         local cannon = df.building.find(job.general_refs[0].building_id)
  161.         local buildingDirection = string.sub(df.global.world.raws.buildings.all[cannon.custom_type].code, -2)
  162.         if job.reaction_name == "GUN_ROTATE_CANNON_R" then
  163.             if buildingDirection == "_N" or buildingDirection == "_E" or buildingDirection == "_S" then
  164.                 cannon.custom_type = cannon.custom_type + 1
  165.             else
  166.                 cannon.custom_type = cannon.custom_type - 3
  167.             end
  168.         else
  169.             if buildingDirection == "_E" or buildingDirection == "_S" or buildingDirection == "_W" then
  170.                 cannon.custom_type = cannon.custom_type - 1
  171.             else
  172.                 cannon.custom_type = cannon.custom_type + 3
  173.             end
  174.         end
  175.     elseif job.reaction_name == "GUN_LOAD_CANNON" then
  176.         if #job.items == 2 then
  177.             job.items[0].item.flags.forbid = true
  178.             job.items[1].item.flags.forbid = true
  179.             cannon = df.building.find(job.general_refs[0].building_id)
  180.             local loadedCount = 0
  181.             for _ in pairs(cannon.contained_items) do loadedCount = loadedCount + 1 end
  182.             if loadedCount > 4 then
  183.                 dfhack.gui.showAnnouncement(getFullName(unit).." cancels load cannon: Already loaded.", 12)        
  184.                 job.items[0].item.flags.forbid = false
  185.                 job.items[1].item.flags.forbid = false
  186.             end
  187.         end
  188.     elseif job.reaction_name == "GASSHELL" then
  189.         for indx, itemRef in ipairs(job.items) do
  190.             local item = itemRef.item
  191.             if tostring(item._type) == "<type: item_trapcompst>" then
  192.                 item:setSubtype(item:getSubtype() + 1) --venom is stored as an item improvement, filled shell is seperate trapcomp to prevent multiple fillings and for graphical reasons
  193.             end
  194.         end
  195.     end
  196. end
  197.  
  198. function getFullName(unit) --doesn't account for heroic names yet because I wasn't sure if they replace the normal surname
  199.     local firstName = unit.name.first_name
  200.     firstName = firstName:sub(1,1):upper()..firstName:sub(2)
  201.     local surname = ""
  202.     for indx, wordID in pairs(unit.name.words) do
  203.         if wordID ~= -1 then
  204.             local word = df.global.world.raws.language.translations[unit.name.language].words[unit.name.words[indx]].value
  205.             if indx == 0 then
  206.                 surname = word:sub(1,1):upper()..word:sub(2)
  207.             elseif indx == 1 then
  208.                 surname = surname..word
  209.             end
  210.         end
  211.     end
  212.     return firstName.." "..surname..", Siege Operator"
  213. end
  214.  
  215. function checkDirection(cannon, target, mode) --mode 1 checks direction of target against cannon orientation, mode 2 returns muzzle position
  216.     local cannon_direction = string.sub(df.global.world.raws.buildings.all[cannon.custom_type].code, -2)
  217.     if cannon_direction == "_N" then --Never
  218.         if mode == 1 then
  219.             if target.pos.y < cannon.centery - minrange then return true else return false end
  220.         elseif mode == 2 then
  221.             return {x = cannon.centerx, y = cannon.centery - 1, z = cannon.z}
  222.         end
  223.     elseif cannon_direction == "_E" then --Eat
  224.         if mode == 1 then
  225.             if target.pos.x > cannon.centerx + minrange then return true else return false end
  226.         elseif mode == 2 then
  227.             return {x = cannon.centerx + 1, y = cannon.centery, z = cannon.z}
  228.         end
  229.     elseif cannon_direction == "_S" then --Shredded
  230.         if mode == 1 then
  231.             if target.pos.y > cannon.centery + minrange then return true else return false end 
  232.         elseif mode == 2 then
  233.             return {x = cannon.centerx, y = cannon.centery + 1, z = cannon.z}
  234.         end
  235.     elseif cannon_direction == "_W" then --Wheat
  236.         if mode == 1 then
  237.             if target.pos.x < cannon.centerx - minrange then return true else return false end
  238.         elseif mode == 2 then
  239.             return {x = cannon.centerx - 1, y = cannon.centery, z = cannon.z}
  240.         end
  241.     end
  242. end
  243.  
  244. function isHostile(firer, target, reaction) --TODO: implement better hostility check
  245.     if reaction == "GUN_FIRE_CANNON" then
  246.         if target.flags2.killed == false then
  247.             if target.civ_id == firer.civ_id or target.flags1.diplomat == true or target.flags1.merchant == true or target.flags2.visitor == true or target.flags2.resident == true then
  248.                 return false
  249.             else
  250.                 return true
  251.             end
  252.         end
  253.     elseif reaction ==  "GUN_TEST_FIRE_CANNON" then
  254.         if target.flags2.killed == false then
  255.             if target.name.first_name ~= "" or target.flags1.diplomat == true or target.flags1.merchant == true or target.flags2.visitor == true or target.flags2.resident == true then
  256.                 return false
  257.             else
  258.                 return true
  259.             end
  260.         end
  261.     end
  262. end
  263.  
  264. function canPathTo(originPos, targetPos, targetDistance)
  265.     local directionVector = df.coord:new()
  266.     directionVector.x = targetPos.x - originPos.x
  267.     directionVector.y = targetPos.y - originPos.y
  268.     directionVector.z = targetPos.z - originPos.z
  269.     local stepAmount = 1 / targetDistance
  270.     local step = stepAmount
  271.     local passCount = 0
  272.     local linePath = df.coord:new()
  273.     while step <= 1 do
  274.         linePath.x = math.floor(originPos.x + (directionVector.x * step) + 0.5)
  275.         linePath.y = math.floor(originPos.y + (directionVector.y * step) + 0.5)
  276.         linePath.z = math.floor(originPos.z + (directionVector.z * step) + 0.5)
  277.         if dfhack.maps.getTileBlock(linePath).walkable[linePath.x%16][linePath.y%16] ~= 0 or dfhack.maps.getTileBlock(linePath).tiletype[linePath.x%16][linePath.y%16] == 32 then
  278.             passCount = passCount + 1
  279.         end
  280.         step = step + stepAmount
  281.     end
  282.     if passCount == 1 / stepAmount then
  283.         return true
  284.     else
  285.         return false
  286.     end
  287. end
  288.  
  289. function findDistance(originDimension, targetDimension)
  290.     local distance
  291.     if originDimension > targetDimension then
  292.         distance = originDimension - targetDimension
  293.     else
  294.         distance = targetDimension - originDimension
  295.     end
  296.     return distance
  297. end
  298.  
  299. function cannonSmoke(projectile, type, amount)
  300.     if projectile.item:getSubtype() ~= -1 and tostring(dfhack.items.getSubtypeDef(projectile.item:getType(),projectile.item:getSubtype())._type) == "<type: itemdef_trapcompst>" then
  301.         if type == 10 then --gas cannisters
  302.             local matType = projectile.item.improvements[0].mat_type
  303.             local matIndex = projectile.item.improvements[0].mat_index
  304.             local northFlow = dfhack.maps.spawnFlow(projectile.cur_pos, type, matType, matIndex, amount)
  305.                 northFlow.density = gasDensity
  306.                 northFlow.dest = {x = projectile.target_pos.x, y = projectile.target_pos.y - gasSpread, z = projectile.target_pos.z}
  307.             local eastFlow = dfhack.maps.spawnFlow(projectile.cur_pos, type, matType, matIndex, amount)
  308.                 eastFlow.density = gasDensity
  309.                 eastFlow.dest = {x = projectile.target_pos.x - gasSpread, y = projectile.target_pos.y, z = projectile.target_pos.z}        
  310.             local southFlow = dfhack.maps.spawnFlow(projectile.cur_pos, type, matType, matIndex, amount)
  311.                 southFlow.density = gasDensity
  312.                 southFlow.dest = {x = projectile.target_pos.x, y = projectile.target_pos.y + gasSpread, z = projectile.target_pos.z}           
  313.             local westFlow = dfhack.maps.spawnFlow(projectile.cur_pos, type, matType, matIndex, amount)
  314.                 westFlow.density = gasDensity
  315.                 westFlow.dest = {x = projectile.target_pos.x + gasSpread, y = projectile.target_pos.y, z = projectile.target_pos.z}
  316.         else
  317.             dfhack.maps.spawnFlow(projectile.cur_pos, type, -1, -1, amount) --other ammo types
  318.         end
  319.     end
  320. end
  321.  
  322. function fireProjectile(item, muzzlePos, target)
  323.     local projectile = dfhack.items.makeProjectile(item)
  324.     projectile.origin_pos.x = muzzlePos.x
  325.     projectile.origin_pos.y = muzzlePos.y
  326.     projectile.origin_pos.z = muzzlePos.z
  327.     projectile.prev_pos.x = muzzlePos.x
  328.     projectile.prev_pos.y = muzzlePos.y
  329.     projectile.prev_pos.z = muzzlePos.z
  330.     projectile.cur_pos.x = muzzlePos.x
  331.     projectile.cur_pos.y = muzzlePos.y
  332.     projectile.cur_pos.z = muzzlePos.z
  333.     projectile.target_pos.x = target.pos.x
  334.     projectile.target_pos.y = target.pos.y
  335.     projectile.target_pos.z = target.pos.z
  336.     projectile.flags.no_impact_destroy=false
  337.     projectile.flags.bouncing=false
  338.     projectile.flags.piercing=true
  339.     projectile.flags.parabolic=false
  340.     projectile.flags.unk9=false
  341.     projectile.flags.no_collide=false
  342.     projectile.distance_flown=0
  343.     projectile.fall_threshold=maxrange
  344.     projectile.min_hit_distance=minrange
  345.     projectile.min_ground_distance=maxrange-1
  346.     projectile.fall_counter=0
  347.     projectile.fall_delay=0
  348.     projectile.hit_rating=max_hitrate
  349.     projectile.unk22 = velocity
  350.     projectile.speed_x=0
  351.     projectile.speed_y=0
  352.     projectile.speed_z=0
  353. end
  354.  
  355. function printTargetDebug(firer, nearestUnit, nearestDistance)
  356.     print(firer.name.first_name.." fired cannon from: ")
  357.     print(pos2xyz(firer.pos))
  358.     print("  at:")
  359.     if nearestUnit.name.first_name ~= "" then
  360.         print(" "..nearestUnit.name.first_name)
  361.     else
  362.         print(" Creature")
  363.     end
  364.     print(" race: "..tostring(df.global.world.raws.creatures.all[nearestUnit.race].name[0]))
  365.     print(" distance: "..tostring(nearestDistance))
  366. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement