Bay 12 Games Forum

Please login or register.

Login with username, password and session length
Advanced search  
Pages: 1 ... 35 36 [37] 38 39 ... 42

Author Topic: [DFHack] Roses' Script Collection Updated 5/4/15  (Read 120113 times)

expwnent

  • Bay Watcher
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #540 on: February 01, 2015, 02:08:27 pm »

Move your hack/lua files to raw/scripts/library and use dfhack.script_environment("library/blah").function(args) for basic usage. See LUA API.rst for details.

Seems simple enough. Can a library function use another library function?

Yes, and you can even have circular dependencies. f1 in file1 can call f2 in file2 which sometimes calls F1 in file1. There is no longer any distinction between scripts and libraries (except for libraries in hack/lua which have to be 'require'd).

It it slightly safer to store script_environment variables in a local variable than a global variable because the version of the library you're calling could change if the savegame changes. The script_environment function is fast so don't worry about optimizing it out. Do store it in a local variable if you're calling many functions or calling one function many times, but don't store it in a global. It will almost never be an issue but it's best not to get into bad habits.

If you do store it in a global variable then you can't have circular dependencies because just loading one script will require loading the other first.
Logged

expwnent

  • Bay Watcher
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #541 on: February 01, 2015, 05:19:42 pm »

I'm building reaction-product-trigger on top of the old Lua hook system and I'm not sure of the best way to do things.

Do you just need to see the list of produced items or do you need to be able to veto a reaction before it produces items? If you veto it then it should prevent the reagents from being destroyed and even prevent experience. You can even do weird things like make the reaction happen "for free" without destroying the reagents. From Lua, it's easy enough to have all that power, but I'm not sure how much of that power registration commands need. It would be difficult to specify on the command line under what conditions you want to veto the item creation.

The way it works now (with my recent unpublished tweaks) is that for each reaction product actually produced it does callbacks. If the product has a probability and doesn't produce itself the callback doesn't happen. This is the main difference from reaction-trigger and the reason why I'm going to keep both. The new change is that instead of just doing callbacks before the product is produced, it also does callbacks just after using the same function with different arguments so you can tell what happened.

It's a little awkward because it doesn't do one callback giving you the full list of products, it gives you one callback per item produced (stacks count as one) and the list of products produced so far. If you're doing very advanced stuff you can make a dummy "last product" rock or something and delete it once produced and know that by that point you have the full list of items produced but that's a little ugly.

There's no easy way to always correctly detect the number of products that will be produced when probabilities are involved. I don't know how to look at a building and tell what fractions of products are there and even if I knew that's an ugly awkward way of doing things.
Logged

Roses

  • Bay Watcher
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #542 on: February 01, 2015, 06:32:57 pm »

Move your hack/lua files to raw/scripts/library and use dfhack.script_environment("library/blah").function(args) for basic usage. See LUA API.rst for details.

Seems simple enough. Can a library function use another library function?

Yes, and you can even have circular dependencies. f1 in file1 can call f2 in file2 which sometimes calls F1 in file1. There is no longer any distinction between scripts and libraries (except for libraries in hack/lua which have to be 'require'd).

It it slightly safer to store script_environment variables in a local variable than a global variable because the version of the library you're calling could change if the savegame changes. The script_environment function is fast so don't worry about optimizing it out. Do store it in a local variable if you're calling many functions or calling one function many times, but don't store it in a global. It will almost never be an issue but it's best not to get into bad habits.

If you do store it in a global variable then you can't have circular dependencies because just loading one script will require loading the other first.

Sounds good, so basically wherever I have a function = require 'blah', I can delete that, and where I call the function simply put dfhack.script_environment("library/blah").function(args). Is that all that needs to be done? I will test it out this week. This shouldn't change the end users experience at all right?

I'm building reaction-product-trigger on top of the old Lua hook system and I'm not sure of the best way to do things.

Do you just need to see the list of produced items or do you need to be able to veto a reaction before it produces items? If you veto it then it should prevent the reagents from being destroyed and even prevent experience. You can even do weird things like make the reaction happen "for free" without destroying the reagents. From Lua, it's easy enough to have all that power, but I'm not sure how much of that power registration commands need. It would be difficult to specify on the command line under what conditions you want to veto the item creation.

The way it works now (with my recent unpublished tweaks) is that for each reaction product actually produced it does callbacks. If the product has a probability and doesn't produce itself the callback doesn't happen. This is the main difference from reaction-trigger and the reason why I'm going to keep both. The new change is that instead of just doing callbacks before the product is produced, it also does callbacks just after using the same function with different arguments so you can tell what happened.

It's a little awkward because it doesn't do one callback giving you the full list of products, it gives you one callback per item produced (stacks count as one) and the list of products produced so far. If you're doing very advanced stuff you can make a dummy "last product" rock or something and delete it once produced and know that by that point you have the full list of items produced but that's a little ugly.

There's no easy way to always correctly detect the number of products that will be produced when probabilities are involved. I don't know how to look at a building and tell what fractions of products are there and even if I knew that's an ugly awkward way of doing things.

Basically, what I would like to see is this.

1. A list of all reagents (the actual item or item itself). And any flags that were set in the reaction (i.e. PRESERVE_REAGENT)
2. A way to tie that list into reaction-trigger (i.e. \\REAGENT_A or something to tell a script what item id to use)
3. Similarly, a way to run a script, make some sort of check, and if the check is passed destroy the reagents that had PRESERVE_REAGENT. This is basically the same as #2
4. A way to identify products by their item id, I don't currently have any scripts that use the products for something, but I remember a couple people asking about it, and I could envision some things.
5. A way to identify the building that the reaction was run at.

With #1, #2, and #5 I would be able to port all of my base/ scripts into the new system, and people wouldn't need to use inorganics.
Logged

expwnent

  • Bay Watcher
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #543 on: February 01, 2015, 08:48:41 pm »

Sounds good, so basically wherever I have a function = require 'blah', I can delete that, and where I call the function simply put dfhack.script_environment("library/blah").function(args). Is that all that needs to be done? I will test it out this week. This shouldn't change the end users experience at all right?

Correct. If it's a DFHack builtin library you should still use require, otherwise you should make it a local script. I strongly recommend putting it in raw/scripts/library if it doesn't have any functionality to the end user but that's just a convention.



I've been iterating through this post enough times I've lost track of what I'm saying. Most important bit: I'd like to have one syntax that works well for both calling a script and calling a function declared in a script. Any ideas? This is in the context of "modtools/*-trigger -command blah".

Basically, what I would like to see is this.

1. A list of all reagents (the actual item or item itself). And any flags that were set in the reaction (i.e. PRESERVE_REAGENT)
2. A way to tie that list into reaction-trigger (i.e. \\REAGENT_A or something to tell a script what item id to use)
3. Similarly, a way to run a script, make some sort of check, and if the check is passed destroy the reagents that had PRESERVE_REAGENT. This is basically the same as #2
4. A way to identify products by their item id, I don't currently have any scripts that use the products for something, but I remember a couple people asking about it, and I could envision some things.
5. A way to identify the building that the reaction was run at.

With #1, #2, and #5 I would be able to port all of my base/ scripts into the new system, and people wouldn't need to use inorganics.

1. Reagents means the inputs right? That's easy enough.

2. Specific indicies or the full list should be fine.

5. Easy enough to find the building based on the unit who did the job.

3. Running the script and having it return a value is a little more intricate. Right now I'm thinking something that directly calls a function using script_environment.

Suppose you want a reaction to only work for vampires and to cancel itself if run by a nonvampire.

Code: [Select]
modtools/reaction-product-trigger -reactionName CHEAT_STONE -product 1 -precondition [ dfhack.modules.units isVampire [ \\WORKER ] ] -keepReagentsOnFail

------elsewhere this makes this happen
local env = dfhack.script_environment('dfhack.modules.units') --CURRENTLY WRONG: this requires a 'require' not a script_environment. leaving it for an example if it did work that way and we got rid of require entirely
local b = env['isVampire'](worker) --note we can pass the actual worker pointer: we do not have to pass the id of the worker in string format instead like with run_script
call_native.value = call_native.value and b
if not b then
  input_items.resize(0) --make it so that DF loses track of the reagents so it can't delete them
  --Just setting call_native to false and not clearing the input items means that nonvampires have to pay the cost but don't get the output.
  --This is reasonable if you want vampires to get an extra output with a given reaction but not what we want in this example
end

It's complicated because we have to call a function, not a script, and we care about the return value.

After the product is created we want to call scripts that have access to the vector of the created items (there could be more than one if you have PRODUCT:100:someLargeNumber:etc).

Code: [Select]
modtools/reaction-product-trigger -reactionName CHEAT_STONE -product 1 -afterCreate [ someScript -reactionName \\PRODUCT \\REACTION_NAME -productName \\PRODUCT_NAME -itemIds \\OUTPUT_ITEMS ]
modtools/reaction-product-trigger -reactionName CHEAT_STONE -product 2 -afterCreate [ deleteItem -itemId \\OUTPUT_ITEM ]



In other news I'm considering updating *-trigger so that it only calls functions instead of scripts. After my new tweaks, if you call dfhack.script_environment(scriptName).runScript(args) it's equivalent to dfhack.run_script(scriptName, args). If you really want to call run_script you still can but having one interface is usually better than having two that do about the same thing. It's a simpler design.

Then again the syntax will probably be more complicated and I don't want to scare off modders. I'd like to have one syntax that works well for both calling a script and calling a function declared in a script. Any ideas?

Technically you can do

Code: [Select]
devel/anonymous-script "dfhack.getEnvironment('blah').function(...)"

but that's horrible and ugly and it doesn't allow passing nonstring values.

I'm also considering making callbacks from eventful pass one argument table with named arguments so that I can add and reorder the arguments without breaking compatibility. It would break everything once though.
Logged

expwnent

  • Bay Watcher
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #544 on: February 02, 2015, 12:48:20 am »

I'm overcomplicating this. New plan: one callback after the product is created and before reagents are destroyed. You can do any combination of deleting the output and preserving the input. You cannot prevent the items from being created in the first place and you cannot prevent the unit from gaining experience.

You will get one callback per product raw. You will not get a callback if the product is not created due to the probability tag in the raws.

Maybe support for calling functions instead of scripts in *-trigger scripts later.
« Last Edit: February 02, 2015, 12:50:34 am by expwnent »
Logged

Roses

  • Bay Watcher
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #545 on: February 02, 2015, 12:36:54 pm »

I'm overcomplicating this. New plan: one callback after the product is created and before reagents are destroyed. You can do any combination of deleting the output and preserving the input. You cannot prevent the items from being created in the first place and you cannot prevent the unit from gaining experience.

You will get one callback per product raw. You will not get a callback if the product is not created due to the probability tag in the raws.

Maybe support for calling functions instead of scripts in *-trigger scripts later.

That sounds perfect, with one caveat. What happens if there is no product? Will the callback not be generated? I only ask because I know some people are trying to get away from having to have temperature set to on, and for some people they may not want a product.

Actually, nevermind, I just read "You can do any combination of deleting the output and preserving the input" so you can just delete the product. Excellent!
Logged

Roses

  • Bay Watcher
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #546 on: February 02, 2015, 04:17:01 pm »

@expwnent: On a side note, I am starting to expand outside-only into a sort of building-trigger, where instead of just specifying outside or inside you can specify a set number of them, require water/magma, and other requirements. I just wanted to check something really quick.

In the API I found removeNative(shop_name), which I am assuming makes it so you no longer see that building as a choice for building. And, addReactionToShop(reaction_name,shop_name), which I assume adds a useable reaction to a building that previously could not do that reaction. Is this correct? And my real question, is there a way to add a building to the building list during gameplay? Like a reverse removeNative? Also, is there a reverse addReactionToShop?
Logged

expwnent

  • Bay Watcher
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #547 on: February 03, 2015, 01:28:46 am »

The thing is up basically on github in the develop branch.

building-trigger would be cool. I don't know much about that stuff. Warmist wrote all that.
Logged

Warmist

  • Bay Watcher
  • Master of unfinished jobs
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #548 on: February 03, 2015, 01:33:15 am »

@expwnent: On a side note, I am starting to expand outside-only into a sort of building-trigger, where instead of just specifying outside or inside you can specify a set number of them, require water/magma, and other requirements. I just wanted to check something really quick.

In the API I found removeNative(shop_name), which I am assuming makes it so you no longer see that building as a choice for building. And, addReactionToShop(reaction_name,shop_name), which I assume adds a useable reaction to a building that previously could not do that reaction. Is this correct? And my real question, is there a way to add a building to the building list during gameplay? Like a reverse removeNative? Also, is there a reverse addReactionToShop?
Actually removeNative is reverse to addReactionToShop. I.e. it removes ALL the native reactions from the shop.

Roses

  • Bay Watcher
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #549 on: February 03, 2015, 12:06:37 pm »

The thing is up basically on github in the develop branch.

building-trigger would be cool. I don't know much about that stuff. Warmist wrote all that.
Is there documentation for it so I can see how it will work to call items and such?

@expwnent: On a side note, I am starting to expand outside-only into a sort of building-trigger, where instead of just specifying outside or inside you can specify a set number of them, require water/magma, and other requirements. I just wanted to check something really quick.

In the API I found removeNative(shop_name), which I am assuming makes it so you no longer see that building as a choice for building. And, addReactionToShop(reaction_name,shop_name), which I assume adds a useable reaction to a building that previously could not do that reaction. Is this correct? And my real question, is there a way to add a building to the building list during gameplay? Like a reverse removeNative? Also, is there a reverse addReactionToShop?
Actually removeNative is reverse to addReactionToShop. I.e. it removes ALL the native reactions from the shop.
Interesting, does this remove the shop from the list of available buildings to build, or does it simple mean building the building is pointless?

On another side note, I'd like an opinion on naming convention. Right now I am putting all scripts relevant to a certain thing in their own respective folders (i.e. all item based scripts are in item/) I like this conventions because it is easier for me to organize myself. My question is, what about the scripts themselves. For example in units/ I have attribute-change, trait-change, body-change etc... but should those be change-attribute, change-trait, change-body? I only ask because there are other, non-changing scripts, like remove and create, and possibly others. What are peoples opinions? (Of course I am leaning to just not changing anything because that is easier, but I can also just write a quick reg-ex script to change all instances of attribute-change to change-attribute)
Logged

Warmist

  • Bay Watcher
  • Master of unfinished jobs
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #550 on: February 03, 2015, 12:24:31 pm »

Yeah building the building is pointless then. Though with some advanced thinking you might get it to dissapear. I mean it's probably something along the lines:
Code: [Select]
detect viewscreen change
remove buildings from ui menu

Roses

  • Bay Watcher
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #551 on: February 03, 2015, 02:18:07 pm »

Well this is my first pass at building-trigger. Thoughts?

Code: [Select]
--scripts/modtools/building-trigger.lua
--original author expwnent (outside-only) expanded by roses
--enables restriction on buildings

local eventful = require 'plugins.eventful'
local utils = require 'utils'

buildingLocation = buildingLocation or utils.invert({'EITHER','OUTSIDE_ONLY','INSIDE_ONLY'})
buildingLiquid = buildingLiquid or utils.invert({'EITHER','MAGMA','WATER'})
registeredBuildings = registeredBuildings or {}
checkEvery = checkEvery or 100
timeoutId = timeoutId or nil

eventful.enableEvent(eventful.eventType.UNLOAD,1)
eventful.onUnload.outsideOnly = function()
 registeredBuildings = {}
 checkEvery = 100
 timeoutId = nil
end

local function destroy(building)
 if #building.jobs > 0 and building.jobs[0] and building.jobs[0].job_type == df.job_type.DestroyBuilding then
  return
 end
 local b = dfhack.buildings.deconstruct(building)
 if b then
  --TODO: print an error message to the user so they know
  return
 end
-- building.flags.almost_deleted = 1
end

local function checkBuildings()
 local toDestroy = {}
 local function forEach(building)
  if building:getCustomType() < 0 then
   --TODO: support builtin building types if someone wants
   print('vanilla buildings not currently supported')
   return
  end
  local pos = df.coord:new()
  pos.x = building.centerx
  pos.y = building.centery
  pos.z = building.z
  local outside = dfhack.maps.getTileBlock(pos).designation[pos.x%16][pos.y%16].outside
  local def = df.global.world.raws.buildings.all[building:getCustomType()]
  local blocation = registeredBuildings[def.code].location
  local bnumber = registeredBuildings[def.code].number
  local bliquid = registeredBuildings[def.code].liquid
  if btype then
--   print('outside: ' .. outside==true .. ', type: ' .. btype)
  end

  if not blocation and not bnumber and not bliquid then
   registeredBuildings[def.code] = nil
   return
  end
 
  if blocation == buildingLocation.EITHER then
   return
  elseif blocation == buildingLocation.OUTSIDE_ONLY then
   if outside then
    return
   end
  else
   if not outside then
    return
   end
  end
  if bnumber then
   local number = 0
   for _,k in ipairs(df.global.world.buildings.all) do
    if k.custom_type == building.custom_type then number = number + 1 end
   end
   if bnumber > number then return end
  end
  if bliquid then
   for i = building.x1-1,building.x2+1,1 do
    for j = building.y1-1,building.y2+1,1 do
local designation = dfhack.maps.getTileBlock(i,j,building.z-1).designation[i%16][j%16]
if designation.flow_size > 3 then
  if bliquid == buildingLiquid.EITHER then
   return
  elseif bliquid == buildingLiquid.MAGMA then
   if designation.liquid_type then
    return
   end
  else
   if not designation.liquid_type then
    return
   end
  end
end
end
   end
  end  
  table.insert(toDestroy,building)
 end
 for _,building in ipairs(df.global.world.buildings.all) do
  forEach(building)
 end
 for _,building in ipairs(toDestroy) do
  destroy(building)
 end
 if timeoutId then
  dfhack.timeout_active(timeoutId,nil)
  timeoutId = nil
 end
 timeoutId = dfhack.timeout(checkEvery, 'ticks', checkBuildings)
end

eventful.enableEvent(eventful.eventType.BUILDING, 100)
eventful.onBuildingCreatedDestroyed.outsideOnly = function(buildingId)
 checkBuildings()
end

validArgs = validArgs or utils.invert({
 'help',
 'clear',
 'checkEvery',
 'building',
 'location''
 'number',
 'liquid',
})
local args = utils.processArgs({...}, validArgs)
if args.help then
 print([[scripts/modtools/outside-only
arguments
    -help
        print this help message
    -clear
        clears the list of registered buildings
    -checkEvery n
        set how often existing buildings are checked for whether they are in the appropriate location to n ticks
    -location [EITHER, OUTSIDE_ONLY, INSIDE_ONLY]
        specify what sort of location restriction to put on the building
    -liquid [EITHER, MAGMA, WATER]
        specify what sort of liquid restriction to put on the building
    -number #
        specify maximum number of buildings of this type allowed
    -building name
        specify the id of the building
]])
 return
end

if args.clear then
 registeredBuildings = {}
end

if args.checkEvery then
 if not tonumber(args.checkEvery) then
  error('Invalid checkEvery.')
 end
 checkEvery = tonumber(args.checkEvery)
end

if not args.building then
 return
end

if not args.location and not args.number and not args.liquid then
 print 'building-trigger: please specify some type'
 return
end

if not buildingLocation[args.location] then
 error('Invalid location type: ' .. args.location)
 return
end
if not buildingLiquid[args.liquid] then
 error('Invalid liquid type: ' .. args.liquid)
 return
end
registeredBuildings[args.building] = {}
if args.location then registeredBuildings[args.building].location = buildingLocation[args.location] end
if args.liquid then registeredBuildings[args.building].liquid = buildingLiquid[args.liquid] end
registeredBuildings[args.building].number = args.number

checkBuildings()


EDIT: I am also sorry to say that, while most of my scripts updated versions are identical to the previous for the end user, there are a couple that unfortunately needed to be changed. I full list will be included when I submit the update, but currently the list includes all scripts in the special/ folder. Luckily most of the changes can be done with a simple find->replace, which I will also include with the update. (Note: none of the Class, Civilization, Event, or Wrapper systems syntax will be changing).

EDIT2: I am toying around with the idea of "unlearning" spells when you switch classes. It wouldn't be true unlearning, you would just no longer be able to perform the interaction if you are not of the appropriate class. If you switched back to the class, and had already learned the spell, you would automatically learn it again. As an example
Unit 1 becomes a Warrior
Unit 1 spends X skill points to learn Shield Bash
Unit 1 spends Y skill points to learn Parry
Unit 1 becomes a Mage
Unit 1 forgets Shield Bash and Parry
Unit 1 spends Z skill points to learn Fire Ball
Unit 1 becomes a Warrior
Unit 1 learns Shield Bash
Unit 1 learns Parry
Unit 1 forgets Fire Ball
Unit 1 becomes a Mage-Warrior
Unit 1 learns Fire Ball
Unit 1 spends W skill points to learn Freeze
etc...

This would prevent units from becoming super powerful and so they didn't have too many interactions to use that they don't do anything else.

EDIT3: Slight update, I have combined special/projectile and special/falling into one script (since they basically did the same thing). I have also modified the combined script to be able to use equipped items. So if you have a stack of arrows you can use them in interactions for "special" attacks. (e.g. an Aimed Shot which uses an arrow like a normal shot, but has a 100% hit chance, or a Power Shot which has a longer range and higher velocity). You can also make throwing attacks.
« Last Edit: February 04, 2015, 05:05:17 pm by Roses »
Logged

Putnam

  • Bay Watcher
  • DAT WIZARD
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #552 on: February 05, 2015, 03:11:42 am »

I'm having another problem with the class system, this time being that it occasionally gets me an error in read-file that classes (classes = persistTable.GlobalTable.roses.ClassTable) is nil. This is happening, as far as I can tell, because read-file is somehow being loaded... wrongly, so it simply calls the read_file function from the library instead of properly running the whole thing and defining persistTable.GlobalTable.roses.ClassTable. I figure this might be fixed with the moveover to an all-script system, but even so, I would recommend putting the definition of persistTable.GlobalTable.roses.ClassTable into the read_file function.

Roses

  • Bay Watcher
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #553 on: February 05, 2015, 12:41:31 pm »

Yes, I started seeing that error as well and have already made that change. It is also worth noting that my other changes have made the system about 4x faster. And when I put out this release (just working on some documentation to go with it) the way you enable the systems will be different.

Instead of having
Code: [Select]
base/classes
base/civilizations
base/events
base/persist-delay

You will simply have
Code: [Select]
base/roses-init -classSystem -civilizationSystem -eventSystem -persistDelayOr
Code: [Select]
base/roses-init -allFor short

There will also be an option -forceReload which will bypass the new checks I put in to detect if the systems have already been loaded.

EDIT: Also Putnam, I changed the way spells work. They are now non-permanent and are lost upon changing class (unless the new class has access to the spell as well). But since I didn't want to mess up all the work you did I made it so that it is easily commented out (I may even just make it an option). When I upload the update I will tell you exactly which lines to comment out. Sorry for the inconvenience, I couldn't think of a better way to handle it.
« Last Edit: February 05, 2015, 04:19:57 pm by Roses »
Logged

TheFlame52

  • Bay Watcher
  • Master of the randomly generated
    • View Profile
Re: [DFHack] Roses' Script Collection Updated 12/11/15
« Reply #554 on: February 19, 2015, 07:35:05 pm »

How do I use teleport? I can't seem to figure out the format. Can it even be used from the DFhack console?
Pages: 1 ... 35 36 [37] 38 39 ... 42