I have a game I've been working on for awhile. The core is C++, but I'm using Python for scripting and for Attacks/StatusEffects/Items etc.
I've kind of coded myself into a corner where I'm having to use eval to get the behaviout I want. Here's how it's arising:
I have an xml document that I use to spec attacks, like so:
<Attack name="Megiddo Flare" mp="144" accuracy="1.5" targetting="EnemyParty">
<Flags>
<IgnoreElements/>
<Unreflectable/>
<ConstantDamage/>
<LockedTargetting/>
</Flags>
<Components>
<ElementalWeightComponent>
<Element name="Fire" weight="0.5"/>
</ElementalWeightComponent>
<ConstantDamageCalculatorComponent damage="9995" index="DamageCalculatorComponent"/>
</Components>
</Attack>
I parse this file in python, and build my Attacks. Each Attack consist of any number of Components to implement different behaviour. In this Attack's case, I implement a DamageCalculatorComponent, telling python to use the ConstantDamage variant. I implement all these components in my script files. This is all well and good for component types I'm going to use often. There are some attacks where that attack will be the only attack to use that particular Component Variant. Rather then adding the component to my script files, I wanted to be able to specify the Component class in the xml file.
For instance, If I were to implement the classic White Wind attack from Final Fantasy (restores team HP by the amount of HP of the attacker)
<Attack name="White Wind" mp="41" targetting="AnyParty">
<Flags>
<LockedTargetting/>
</Flags>
<Components>
<CustomComponent index="DamageCalculatorComponent">
<![CDATA[
class WhiteWindDamageComponent(DamageCalculatorComponent):
def __init__(self, Owner):
DamageCalculatorComponent.__init__(self, Owner)
def CalculateDamage(self, Action, Mechanics):
Dmg = 0
character = Action.GetUsers().GetFirst()
SM = character.GetComponent("StatManagerComponent")
if (SM != None):
Dmg = -SM.GetCurrentHP()
return Dmg
return WhiteWindDamageComponent(Owner)
]]>
</CustomComponent>
</Components>
</Attack>
I was wondering if there might be a better way to do this? The only other way I can see is too put every possible Component variant definition into my python files, and expand my Component creators to check for the additional variant. Seems abit wasteful for a single use Component. Is there a better/safer alternative to generating types dynamically, or perhaps another solution I'm not seeing?
Thanks in advance
Inline Python is bad because
Your source code files cannot be understood by Python source code editors and you will miss syntax highlighting
All other tools, like pylint, which can be used to lint and validate source code will fail also
Alternative
In element <CustomComponent index="DamageCalculatorComponent">
... add parameter script:
<CustomComponent index="DamageCalculatorComponent" script="damager.py">
Then add file damager.py somewhere along the file system.
Load it as described here: What is an alternative to execfile in Python 3?
In your main game engine code construct the class out of loaded system module like:
damager_class = sys.modules["mymodulename"].my_factory_function()
All Python modules must share some kind of agreed class names / entry point functions.
If you want to have really pluggable architecture, use Python eggs and setup.py entry points
http://wiki.pylonshq.com/display/pylonscookbook/Using+Entry+Points+to+Write+Plugins
Example
https://github.com/miohtama/vvv/blob/master/setup.py
Related
I read that Unit tests run fast. If they don’t run fast, they aren’t unit tests. A test is not a unit test if 1. It talks to a database. 2. It communicates across a network. 3. It touches the file system. 4. You have to do special things to your environment (such as editing configuration files) to run it. in Working Effectively with legacy code (book).
I have a function that is downloading the zip from the internet and then converting it into a python object for a particular class.
import typing as t
def get_book_objects(date: str) -> t.List[Book]:
# download the zip with the date from the endpoint
res = requests.get(f"HTTP-URL-{date}")
# code to read the response content in BytesIO and then use the ZipFile module
# to extract data.
# parse the data and return a list of Book object
return books
let's say I want to write a unit test for the function get_book_objects. Then how am I supposed to write a unit test without making a network request? I mean I prefer file system read-over a network request because it will be way faster than making a request to the network although it is written that a good unit test also not touches the file system I will be fine with that.
So even if I want to write a unit test where I can provide a local zip file I have to modify the existing function to open the file from the local file system or I have to add some additional parameter to the function so I can send a zip file path from unit test function.
What will you do to write a good unit test in this kind of situation?
What will you do to write a good unit test in this kind of situation?
In the TDD world, the usual answer would be to delegate the work to a more easily tested component.
Consider:
def get_book_objects(date: str) -> t.List[Book]:
# This is the piece that makes get_book_objects hard
# to isolate
http_get = requests.get
# download the zip with the date from the endpoint
res = http_get(f"HTTP-URL-{date}")
# code to read the response content in BytesIO and then use the ZipFile module
# to extract data.
# parse the data and return a list of Book object
return books
which might then become something like
def get_book_objects(date: str) -> t.List[Book]:
# This is the piece that makes get_book_objects hard
# to isolate
http_get = requests.get
return get_book_objects_v2(http_get, date)
def get_book_objects_v2(http_get, date: str) -> t.List[Book]
# download the zip with the date from the endpoint
res = http_get(f"HTTP-URL-{date}")
# code to read the response content in BytesIO and then use the ZipFile module
# to extract data.
# parse the data and return a list of Book object
return books
get_book_objects is still hard to test, but it is also "so simple that there are obviously no deficiencies". On the other hand, get_book_objects_v2 is easy to test, because your test can control what callable is passed to the subject, and can use any reasonable substitute you like.
What we've done is shift most of the complexity/risk into a "unit" that is easier to test. For the function that is still hard to test, we'll use other techniques.
When authors talk about tests "driving" the design, this is one example - we're treating "complicated code needs to be easy to test" as a constraint on our design.
You've already identified the correct reference (Working Effectively with Legacy Code). The material you want is the discussion of seams.
A seam is a place where you can alter behavior in your program without editing in that place.
(In my edition of the book, the discussion begins in Chapter 4).
I have been looking for hours how to perform a very simple script in Python for 3DS Max (v2017), but the APIs are terrible - to say the least.
I can't even get how to select an existing object in the scene..
Plus I do not understand if I should use pymxs wrapper or MaxPlus.
What I need to do is simply tell to 3ds Max to change a Rendering Effect Attribute when a certain scene camera is selected - or the view is switched to that camera.
I will write you down the script in pseudo code so you can - hopefully - better understand the topic:
camera_1 = MaxPlus.Factory.SelectCameraObject("36x24_MoreDof")
# camera name is 36x24_MoreDof
camera_2 = MaxPlus.Factory.SelectCameraObject("36x24_LessDof")
# camera name is 36x24_LessDof
effect1 = RenderingTab.EnvironmentAndEffects.Effects.Attribute1
effect2 = RenderingTab.EnvironmentAndEffects.Effects.Attribute2
effect1.active = False
effect2.active = False
while True:
if camera_1.isSelected == True:
effect1.active = True
effect2.active = False
elif camera_2.isSelected == True:
effect1.active = False
effect2.active = True
I hope it is clear enough..
Do you have any idea how to translate this in actual Python code for 3DS Max?
Thanks you all in advance,
Riccardo
Below is python script to use as a guide for your request.
PyMXS; my advice is to use this primarily. It wraps almost all of MaxScript (except for some exotic MaxScript syntax features). It has been continuously developed for decades, is robust, and offers a lot of helper functionality and simplifications compared to the 3ds Max SDK which it's build on.
MaxPlus; my advice is to use certain bits of this which aren't covered by PyMXS (for example the function MaxPlus.GetQMaxMainWindow() for creating a Qt-based UI), but don't use this as the primary tool in Python. It is newer, and while it might prove better for Python exposure of the 3ds Max SDK in the long term, you might prefer PyMXS for coverage and simplicity in the short term.
Basically, I recommend thinking of yourself as a MaxScript programmer who uses Python for the language benefits. To access the 3ds Max scene, go through MaxScript (via PyMXS). Then you employ Python's strengths for string processing and data management (dictionaries!). Use MaxPlus when necessary for certain lower-level SDK access.
The script below fetches an object by name, fetches a render effect by index, and enables/disables the render effect according to the object selection:
import pymxs
mxs = pymxs.runtime
object_1 = mxs.getNodeByName( "Camera001" )
effect_1 = mxs.getEffect(1)
effect_1.camera
mxs.setActive(effect_1, mxs.false)
if object_1.isSelected:
mxs.setActive(effect_1, mxs.true)
Hope this helps!
I using the following action to generate ambient occlusion maps for models in maya:
Create and assign aiAmbientOcclusion to my model (the one I want to generate oa maps for).
Then, I go Arnold>Utilities>Render Selection To Texture.
Since this process is always the same I want to write a python script to automate it unfortunately I haven't found many useful examples about writing scripts for Arnold.
To add this functionality I must:
import mtoa.renderToTexture
that script is located in
the_way_to_my_install_folder/solidangle/mtoa/2017/scripts/mtoa
I saw that the script defines the class MtoARenderToTexture and I should pass an object to it. Now.
What kind of object I mush use and is there some sort of documentation for MtoARenderToTexture class?
I was able to do what I wanted using ether this tutorial and extending MtoARenderToTexture class.
I do not iclude all my scripts that load scene and manage scenes files as they very specific to my needs, but still think it's a good idea to share some very basic and fundamental elements that may be useful for some new entries as myself.
This is how my extended class looks like
import mtoa.renderToTexture as renderToTexture
import maya.cmds as cmds
class rkMtoaRtoT(renderToTexture.MtoARenderToTexture):
def __init__(self):
renderToTexture.MtoARenderToTexture.__init__(self)
self.dFolder = '~'
self.dResolution = 1024
self.dCameraSamples = 5
def doAutomaticExport(self):
renderToTexture.MtoARenderToTexture.create(self)
cmds.textFieldButtonGrp('outputFolder', e=True, tx=self.dFolder)
cmds.intFieldGrp('resolution', e=True, v1=self.dResolution)
cmds.intFieldGrp('aa_samples', e=True, v1=self.dCameraSamples)
renderToTexture.MtoARenderToTexture.doExport(self)
So because I have the unity-gtk-module installed, all gtk-applications export their menu over the dbus SessionBus. My goal is to extract a list of all available menu entries. I've already implemented this with the help of pydbus, but for some reason, this solution is highly unstable and some applications just flat out crash. The unity-gtk-module uses Gio's g_dbus_connection_export_menu_model () to export its GMenuModel modeled menu over dbus, so I thought it would make sense to try to use Gio to process the exported menu. Gio uses the GDBusMenuModel class to retrieve a menu from the bus. Python uses PyGObject for wrapping Gio:
from gi.repository import Gio
connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
menuModel = Gio.DBusMenuModel.get(connection, [bus-name e.g. ":1.5"], [object-path e.g. "/com/canonical/unity/gtk/window/0"])
Now menuModel should be wrapping the GMenuModel from my application. At this point I'm honestly a bit confused about how exactly the GMenuModel works (the Description is not really helping) but it seems I have to use a GMenuAttributeIter object to iterate through the entries. But when I try this:
iter = Gio.MenuModel.iterate_item_attributes(menuModel, 0) #0 is the index of the root node
this happens:
GLib-GIO-CRITICAL **: g_dbus_menu_model_get_item_attributes: assertion 'proxy->items' failed
GLib-GIO-CRITICAL **: GMenuModel implementation 'GDBusMenuModel' doesn't override iterate_item_attributes() and fails to return sane calues from get_item_attributes()
This probably happens because GDBusMenuModel inherits GMenuModel which provides these methods, but is abstract, so GDBusMenuModel should override them, which it doesn't (see link above, it provides just g_dbus_menu_model_get ()). If this is the case, how am I supposed to actually use this class as a proxy? And if it's not, what am I doing wrong?
I justed logged in to SO the first time after a few years and remembered that I've actually found a solution to this question (I think). Honestly, I can't remember what half of these words even mean, but at the time I wrote a script to accomplish the task posed in the title, and as far as I remember, in the end, it worked out: https://gist.github.com/encomiastical/caa0ee955300bc2a40ef55d123b06212
tl;dr in bold below
I'm currently developing a text-based adventure game, and I've implemented a basic saving system.
The process takes advantage of the 'pickle' module. It generates or appends to a file with a custom extension (when it is, in reality, a text file).
The engine pickles the player's location, their inventory, and, well, the last part is where it gets a little weird.
The game loads dialog from a specially formatted script (Here I mean as in an actor's script). Some dialog changes based on certain conditions (already spoken to them before, new event, etc.). So, for that third object the engine saves, it saves ALL dialog trees in their current positions. As in, it saves the literal script in its current state.
Here is the saving routine:
with open('save.devl','wb') as file:
pickle.dump((current_pos,player_inv,dia_dict),file)
for room in save_map:
pickle.dump(room,file)
file.close()
My problem is, this process makes a very ugly, very verbose, super large text file. Now I know that text files are basically the smallest files I can generate, but I was wondering if there was any way to compress or otherwise make more efficient the process of recording the state of everything in the game. Or, less preferably but better in the long run, just a smarter way to save the player's data.
The format of dialog was requested. Here is a sample:
[Isaac]
a: Hello.|1. I'm Evan.|b|
b: Nice to meet you.|1. Where are you going?\2.Goodbye.|c,closer|
c: My cousin's wedding.|1. Interesting. Where are you from?\2. What do you know about the ship?\3. Goodbye.|e,closer|
closer: See you later.||break|
e: It's the WPT Magnus. Cruise-class zeppelin. Been in service for about three years, I believe.||c|
standing: Hello, again.|1. What do you know about the ship?\2.Goodbye.|e,closer|
The name in brackets is how the program identifies which tree to call. Each letter is a separate branch in the tree. The bars separate the branch into three parts: 1. What the character says 2. The responses you are allowed 3. Where each response goes, or if the player doesn't respond, where the player is directed afterwards.
In this example, after the player has talked to Isaac, the 'a' branch is erased from the copy of the tree that the game stores in memory. It then permanently uses the 'standing' branch.
Pickle itself has other protocols that are all more compact than the default protocol (protocol 0) - which is the only one "text based" - the others are binary protocols.
But them, you hardly would get more than 50% of the file size - to be able to enhance the answer, we need to know better what you are saving, and if there are smarter ways to save your data - for example, by avoiding repeating the same sub-data structure if it is present in several of your rooms. (Although if you are using object identity inside your game, Pickle should take care of that).
That said, just change your pickle.dump calls to include the protocol parameter - the -1 value is equivalent to "HIGHEST_PROTOCOL", which is usually the most efficient:
pickle.dump(room,file, protocol=-1)
(loading the pickles do not require that the protocol is passed at all)
Aditionally, you might want to use Python's zlib interface to compress pickle data. That could give you another 20-30% file size reduction - you have to chain the calls to file.write, zlib.compress and pickle.dumps, so you will be easier with a little helper code - also you need to control file offsets, as zlib is not like pickle which advances the file pointer:
import pickle, zlib
def store_obj(file_, obj):
compressed = zlib.compress(pickle.dumps(obj, protocol=-1), level=9)
file_.write(len(compressed).to_bytes(4, "little"))
file_.write(compressed)
def get_obj(file_):
obj_size = int.from_bytes(file_.read(4), "little")
if obj_size == 0:
return None
data = zlib.decompress(self.file_.read(obj_size))
return pickle.loads(data)