Compare commits

...

7 Commits

Author SHA1 Message Date
a4b7822715 [Gameplay] Implement a rough comparison function to sort entities within a template-determined percent error
Some checks failed
checkrefs / checkrefs (push) Has been cancelled
pre-commit / build (push) Has been cancelled
2024-09-14 20:32:24 +02:00
e56ebb3f46
Enable ruff naming rules 2024-09-13 11:04:07 +02:00
cd8b4266a4
Fix class name in xmlvalidator 2024-09-13 11:04:06 +02:00
8c7cc7373d
Fix variable names in SPIRV compile.py 2024-09-13 11:04:06 +02:00
0d3e3fbc29
Rename simple-example.py 2024-09-13 11:04:05 +02:00
661328ab15
Fix variable naming for map compatibility file 2024-09-13 11:04:05 +02:00
616f2e134b
Fix variable names in checkrefs.py 2024-09-13 11:04:04 +02:00
15 changed files with 130 additions and 84 deletions

View File

@ -42,6 +42,11 @@ UnitAI.prototype.Schema =
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='RangeError'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<interleave>" +
"<element name='RoamDistance'>" +
@ -3462,6 +3467,7 @@ UnitAI.prototype.Init = function()
this.formationAnimationVariant = undefined;
this.cheeringTime = +(this.template.CheeringTime || 0);
this.rangeError = +(this.template.RangeError || 0);
this.SetStance(this.template.DefaultStance);
};
@ -3776,7 +3782,7 @@ UnitAI.prototype.SetupLOSRangeQuery = function(enable = true)
// Do not compensate for entity sizes: LOS doesn't, and UnitAI relies on that.
this.losRangeQuery = cmpRangeManager.CreateActiveQuery(this.entity,
range.min, range.max, players, IID_Identity,
cmpRangeManager.GetEntityFlagMask("normal"), false);
cmpRangeManager.GetEntityFlagMask("normal"), false, this.rangeError);
if (enable)
cmpRangeManager.EnableActiveQuery(this.losRangeQuery);
@ -3841,7 +3847,7 @@ UnitAI.prototype.SetupAttackRangeQuery = function(enable = true)
// Do not compensate for entity sizes: LOS doesn't, and UnitAI relies on that.
this.losAttackRangeQuery = cmpRangeManager.CreateActiveQuery(this.entity,
range.min, range.max, players, IID_Resistance,
cmpRangeManager.GetEntityFlagMask("normal"), false);
cmpRangeManager.GetEntityFlagMask("normal"), false, this.rangeError);
if (enable)
cmpRangeManager.EnableActiveQuery(this.losAttackRangeQuery);

View File

@ -109,6 +109,7 @@
<CanPatrol>true</CanPatrol>
<PatrolWaitTime>1</PatrolWaitTime>
<CheeringTime>2800</CheeringTime>
<RangeError>10</RangeError>
<Formations datatype="tokens">
special/formations/null
special/formations/box

View File

@ -42,6 +42,7 @@
</Sound>
<Turretable/>
<UnitAI>
<RangeError>15</RangeError>
<Formations datatype="tokens">
special/formations/skirmish
</Formations>

View File

@ -45,6 +45,9 @@
<attack_ranged>attack/weapon/bow_attack.xml</attack_ranged>
</SoundGroups>
</Sound>
<UnitAI>
<RangeError>20</RangeError>
</UnitAI>
<UnitMotion>
<WalkSpeed op="add">0.6</WalkSpeed>
<Acceleration op="add">1.2</Acceleration>

View File

@ -67,6 +67,7 @@
</SoundGroups>
</Sound>
<UnitAI>
<RangeError>3</RangeError>
<DefaultStance>standground</DefaultStance>
</UnitAI>
<UnitMotion>

View File

@ -16,7 +16,7 @@ ignore = [
"FIX",
"FBT",
"ISC001",
"N",
"N817",
"PERF203",
"PERF401",
"PLR0912",

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -169,6 +169,29 @@ public:
return 0;
}
/**
* Returns -1, 0, +1 depending on whether length estimate is less/equal/greater
* than the argument's length.
* Uses a percent error parameter to compare lengths with that percent error.
*/
int CompareLengthRough(const CFixedVector2D& other, u8 rangeError) const
{
u64 d2 = SQUARE_U64_FIXED(X) + SQUARE_U64_FIXED(Y);
u64 od2 = SQUARE_U64_FIXED(other.X) + SQUARE_U64_FIXED(other.Y);
//overflow risk with long ranges (designed for unit ranges)
CheckMultiplicationOverflow(u64, d2, 100+rangeError, "Overflow in CFixedVector2D::CompareLengthRough()","Underflow in CFixedVector2D::CompareLengthRough()")
d2 = d2 * (100-rangeError + d2 % (1+(rangeError*2)));
CheckMultiplicationOverflow(u64, od2, 100, "Overflow in CFixedVector2D::CompareLengthRough()", "Underflow in CFixedVector2D::CompareLengthRough()")
od2 = od2 * 100;
if (d2 < od2)
return -1;
if (d2 > od2)
return +1;
return 0;
}
bool IsZero() const
{
return X.IsZero() && Y.IsZero();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -162,6 +162,7 @@ struct Query
u32 ownersMask;
i32 interface;
u8 flagsMask;
u8 rangeError;
bool enabled;
bool parabolic;
bool accountForSize; // If true, the query accounts for unit sizes, otherwise it treats all entities as points.
@ -255,8 +256,8 @@ static_assert(sizeof(EntityData) == 24);
class EntityDistanceOrdering
{
public:
EntityDistanceOrdering(const EntityMap<EntityData>& entities, const CFixedVector2D& source) :
m_EntityData(entities), m_Source(source)
EntityDistanceOrdering(const EntityMap<EntityData>& entities, const CFixedVector2D& source, u8 rangeError = 0) :
m_EntityData(entities), m_Source(source), m_RangeError(rangeError)
{
}
@ -268,11 +269,12 @@ public:
const EntityData& db = m_EntityData.find(b)->second;
CFixedVector2D vecA = CFixedVector2D(da.x, da.z) - m_Source;
CFixedVector2D vecB = CFixedVector2D(db.x, db.z) - m_Source;
return (vecA.CompareLength(vecB) < 0);
return m_RangeError > 0 ? vecA.CompareLengthRough(vecB, m_RangeError) < 0 : vecA.CompareLength(vecB) < 0;
}
const EntityMap<EntityData>& m_EntityData;
CFixedVector2D m_Source;
u8 m_RangeError;
private:
EntityDistanceOrdering& operator=(const EntityDistanceOrdering&);
@ -294,6 +296,7 @@ struct SerializeHelper<Query>
serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
serialize.NumberI32_Unbounded("interface", value.interface);
Serializer(serialize, "last match", value.lastMatch);
serialize.NumberU8_Unbounded("range percent error", value.rangeError);
serialize.NumberU8_Unbounded("flagsMask", value.flagsMask);
serialize.Bool("enabled", value.enabled);
serialize.Bool("parabolic",value.parabolic);
@ -923,20 +926,20 @@ public:
tag_t CreateActiveQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, u8 flags, bool accountForSize) override
const std::vector<int>& owners, int requiredInterface, u8 flags, bool accountForSize, u8 rangeError) override
{
tag_t id = m_QueryNext++;
m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags, accountForSize);
m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags, accountForSize, rangeError);
return id;
}
tag_t CreateActiveParabolicQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin,
const std::vector<int>& owners, int requiredInterface, u8 flags) override
const std::vector<int>& owners, int requiredInterface, u8 flags, u8 rangeError) override
{
tag_t id = m_QueryNext++;
m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, yOrigin, owners, requiredInterface, flags, true);
m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, yOrigin, owners, requiredInterface, flags, true, rangeError);
return id;
}
@ -993,25 +996,25 @@ public:
std::vector<entity_id_t> ExecuteQueryAroundPos(const CFixedVector2D& pos,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, bool accountForSize) override
const std::vector<int>& owners, int requiredInterface, bool accountForSize, u8 rangeError) override
{
Query q = ConstructQuery(INVALID_ENTITY, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"), accountForSize);
Query q = ConstructQuery(INVALID_ENTITY, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"), accountForSize, rangeError);
std::vector<entity_id_t> r;
PerformQuery(q, r, pos);
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos, q.rangeError));
return r;
}
std::vector<entity_id_t> ExecuteQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, bool accountForSize) override
const std::vector<int>& owners, int requiredInterface, bool accountForSize, u8 rangeError) override
{
PROFILE("ExecuteQuery");
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"), accountForSize);
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"), accountForSize, rangeError);
std::vector<entity_id_t> r;
@ -1026,7 +1029,7 @@ public:
PerformQuery(q, r, pos);
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos, q.rangeError));
return r;
}
@ -1061,7 +1064,7 @@ public:
q.lastMatch = r;
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos, q.rangeError));
return r;
}
@ -1145,7 +1148,7 @@ public:
continue;
if (cmpSourcePosition && cmpSourcePosition->IsInWorld())
std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, cmpSourcePosition->GetPosition2D()));
std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, cmpSourcePosition->GetPosition2D(),query.rangeError));
messages.resize(messages.size() + 1);
std::pair<entity_id_t, CMessageRangeUpdate>& back = messages.back();
@ -1404,7 +1407,7 @@ public:
Query ConstructQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, u8 flagsMask, bool accountForSize) const
const std::vector<int>& owners, int requiredInterface, u8 flagsMask, bool accountForSize, u8 rangeError = 0) const
{
// Min range must be non-negative.
if (minRange < entity_pos_t::Zero())
@ -1453,6 +1456,7 @@ public:
LOGWARNING("CCmpRangeManager: No owners in query for entity %u", source);
q.interface = requiredInterface;
q.rangeError = rangeError;
q.flagsMask = flagsMask;
return q;
@ -1460,9 +1464,9 @@ public:
Query ConstructParabolicQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin,
const std::vector<int>& owners, int requiredInterface, u8 flagsMask, bool accountForSize) const
const std::vector<int>& owners, int requiredInterface, u8 flagsMask, bool accountForSize, u8 rangeError = 0) const
{
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flagsMask, accountForSize);
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flagsMask, accountForSize, rangeError);
q.parabolic = true;
q.yOrigin = yOrigin;
return q;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -132,7 +132,7 @@ public:
* @return list of entities matching the query, ordered by increasing distance from the source entity.
*/
virtual std::vector<entity_id_t> ExecuteQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, bool accountForSize) = 0;
const std::vector<int>& owners, int requiredInterface, bool accountForSize, u8 rangeError = 0) = 0;
/**
* Execute a passive query.
@ -145,7 +145,7 @@ public:
* @return list of entities matching the query, ordered by increasing distance from the source entity.
*/
virtual std::vector<entity_id_t> ExecuteQueryAroundPos(const CFixedVector2D& pos, entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, bool accountForSize) = 0;
const std::vector<int>& owners, int requiredInterface, bool accountForSize, u8 rangeError = 0) = 0;
/**
* Construct an active query. The query will be disabled by default.
@ -159,7 +159,7 @@ public:
* @return unique non-zero identifier of query.
*/
virtual tag_t CreateActiveQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, u8 flags, bool accountForSize) = 0;
const std::vector<int>& owners, int requiredInterface, u8 flags, bool accountForSize, u8 rangeError = 0) = 0;
/**
* Construct an active query of a paraboloic form around the unit.
@ -177,7 +177,7 @@ public:
* @return unique non-zero identifier of query.
*/
virtual tag_t CreateActiveParabolicQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin,
const std::vector<int>& owners, int requiredInterface, u8 flags) = 0;
const std::vector<int>& owners, int requiredInterface, u8 flags, u8 rangeError = 0) = 0;
/**

View File

@ -387,8 +387,8 @@ class CheckRefs:
cmp_auras = entity.find("Auras")
if cmp_auras is not None:
auraString = cmp_auras.text
for aura in auraString.split():
aura_string = cmp_auras.text
for aura in aura_string.split():
if not aura:
continue
if aura.startswith("-"):
@ -397,33 +397,33 @@ class CheckRefs:
cmp_identity = entity.find("Identity")
if cmp_identity is not None:
reqTag = cmp_identity.find("Requirements")
if reqTag is not None:
req_tag = cmp_identity.find("Requirements")
if req_tag is not None:
def parse_requirements(fp, req, recursionDepth=1):
techsTag = req.find("Techs")
if techsTag is not None:
for techTag in techsTag.text.split():
def parse_requirements(fp, req, recursion_depth=1):
techs_tag = req.find("Techs")
if techs_tag is not None:
for tech_tag in techs_tag.text.split():
self.deps.append(
(fp, Path(f"simulation/data/technologies/{techTag}.json"))
(fp, Path(f"simulation/data/technologies/{tech_tag}.json"))
)
if recursionDepth > 0:
recursionDepth -= 1
allReqTag = req.find("All")
if allReqTag is not None:
parse_requirements(fp, allReqTag, recursionDepth)
anyReqTag = req.find("Any")
if anyReqTag is not None:
parse_requirements(fp, anyReqTag, recursionDepth)
if recursion_depth > 0:
recursion_depth -= 1
all_req_tag = req.find("All")
if all_req_tag is not None:
parse_requirements(fp, all_req_tag, recursion_depth)
any_req_tag = req.find("Any")
if any_req_tag is not None:
parse_requirements(fp, any_req_tag, recursion_depth)
parse_requirements(fp, reqTag)
parse_requirements(fp, req_tag)
cmp_researcher = entity.find("Researcher")
if cmp_researcher is not None:
techString = cmp_researcher.find("Technologies")
if techString is not None:
for tech in techString.text.split():
tech_string = cmp_researcher.find("Technologies")
if tech_string is not None:
for tech in tech_string.text.split():
if not tech:
continue
if tech.startswith("-"):

View File

@ -56,13 +56,13 @@ args = parser.parse_args()
HEIGHTMAP_BIT_SHIFT = 3
for xmlFile in args.files:
pmpFile = xmlFile[:-3] + "pmp"
for xml_file in args.files:
pmp_file = xml_file[:-3] + "pmp"
print("Processing " + xmlFile + " ...")
print("Processing " + xml_file + " ...")
if os.path.isfile(pmpFile):
with open(pmpFile, "rb") as f1, open(pmpFile + "~", "wb") as f2:
if os.path.isfile(pmp_file):
with open(pmp_file, "rb") as f1, open(pmp_file + "~", "wb") as f2:
# 4 bytes PSMP to start the file
f2.write(f1.read(4))
@ -73,7 +73,7 @@ for xmlFile in args.files:
elif args.reverse:
if version != 6:
print(
f"Warning: File {pmpFile} was not at version 6, while a negative version "
f"Warning: File {pmp_file} was not at version 6, while a negative version "
f"bump was requested.\nABORTING ..."
)
continue
@ -81,7 +81,7 @@ for xmlFile in args.files:
else:
if version != 5:
print(
f"Warning: File {pmpFile} was not at version 5, while a version bump was "
f"Warning: File {pmp_file} was not at version 5, while a version bump was "
f"requested.\nABORTING ..."
)
continue
@ -122,13 +122,13 @@ for xmlFile in args.files:
f1.close()
# replace the old file, comment to see both files
os.remove(pmpFile)
os.rename(pmpFile + "~", pmpFile)
os.remove(pmp_file)
os.rename(pmp_file + "~", pmp_file)
if os.path.isfile(xmlFile):
if os.path.isfile(xml_file):
with (
open(xmlFile, encoding="utf-8") as f1,
open(xmlFile + "~", "w", encoding="utf-8") as f2,
open(xml_file, encoding="utf-8") as f1,
open(xml_file + "~", "w", encoding="utf-8") as f2,
):
data = f1.read()
@ -137,7 +137,7 @@ for xmlFile in args.files:
if args.reverse:
if data.find('<Scenario version="6">') == -1:
print(
f"Warning: File {xmlFile} was not at version 6, while a negative "
f"Warning: File {xml_file} was not at version 6, while a negative "
f"version bump was requested.\nABORTING ..."
)
sys.exit()
@ -145,7 +145,7 @@ for xmlFile in args.files:
data = data.replace('<Scenario version="6">', '<Scenario version="5">')
elif data.find('<Scenario version="5">') == -1:
print(
f"Warning: File {xmlFile} was not at version 5, while a version bump "
f"Warning: File {xml_file} was not at version 5, while a version bump "
f"was requested.\nABORTING ..."
)
sys.exit()
@ -164,5 +164,5 @@ for xmlFile in args.files:
f2.close()
# replace the old file, comment to see both files
os.remove(xmlFile)
os.rename(xmlFile + "~", xmlFile)
os.remove(xml_file)
os.rename(xml_file + "~", xml_file)

View File

@ -58,4 +58,4 @@ actions = [zero_ad.actions.attack(my_units, enemy_units[0])]
state = game.step(actions)
```
For a more thorough example, check out samples/simple-example.py!
For a more thorough example, check out samples/simple_example.py!

View File

@ -29,6 +29,7 @@ import os
import subprocess
import sys
import xml.etree.ElementTree as ET
from enum import Enum
import yaml
@ -169,18 +170,21 @@ def compile_and_reflect(input_mod_path, dependencies, stage, path, out_path, def
add_push_constants(module["push_constants"][0], push_constants)
descriptor_sets = []
if module.get("descriptor_sets"):
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER = 1
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE = 3
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER = 6
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER = 7
class VkDescriptorType(Enum):
COMBINED_IMAGE_SAMPLER = 1
STORAGE_IMAGE = 3
UNIFORM_BUFFER = 6
STORAGE_BUFFER = 7
for descriptor_set in module["descriptor_sets"]:
UNIFORM_SET = 1 if use_descriptor_indexing else 0
STORAGE_SET = 2
uniform_set = 1 if use_descriptor_indexing else 0
storage_set = 2
bindings = []
if descriptor_set["set"] == UNIFORM_SET:
if descriptor_set["set"] == uniform_set:
assert descriptor_set["binding_count"] > 0
for binding in descriptor_set["bindings"]:
assert binding["set"] == UNIFORM_SET
assert binding["set"] == uniform_set
block = binding["block"]
members = []
for member in block["members"]:
@ -200,15 +204,15 @@ def compile_and_reflect(input_mod_path, dependencies, stage, path, out_path, def
}
)
binding = descriptor_set["bindings"][0]
assert binding["descriptor_type"] == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
elif descriptor_set["set"] == STORAGE_SET:
assert binding["descriptor_type"] == VkDescriptorType.UNIFORM_BUFFER.value
elif descriptor_set["set"] == storage_set:
assert descriptor_set["binding_count"] > 0
for binding in descriptor_set["bindings"]:
is_storage_image = (
binding["descriptor_type"] == VK_DESCRIPTOR_TYPE_STORAGE_IMAGE
binding["descriptor_type"] == VkDescriptorType.STORAGE_IMAGE.value
)
is_storage_buffer = (
binding["descriptor_type"] == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER
binding["descriptor_type"] == VkDescriptorType.STORAGE_BUFFER.value
)
assert is_storage_image or is_storage_buffer
assert (
@ -217,13 +221,13 @@ def compile_and_reflect(input_mod_path, dependencies, stage, path, out_path, def
)
assert binding["image"]["arrayed"] == 0
assert binding["image"]["ms"] == 0
bindingType = "storageImage"
binding_type = "storageImage"
if is_storage_buffer:
bindingType = "storageBuffer"
binding_type = "storageBuffer"
bindings.append(
{
"binding": binding["binding"],
"type": bindingType,
"type": binding_type,
"name": binding["name"],
}
)
@ -232,7 +236,8 @@ def compile_and_reflect(input_mod_path, dependencies, stage, path, out_path, def
assert descriptor_set["binding_count"] >= 1
for binding in descriptor_set["bindings"]:
assert (
binding["descriptor_type"] == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
binding["descriptor_type"]
== VkDescriptorType.COMBINED_IMAGE_SAMPLER.value
)
assert binding["array"]["dims"][0] == 16384
if binding["binding"] == 0:
@ -246,7 +251,9 @@ def compile_and_reflect(input_mod_path, dependencies, stage, path, out_path, def
else:
assert descriptor_set["binding_count"] > 0
for binding in descriptor_set["bindings"]:
assert binding["descriptor_type"] == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
assert (
binding["descriptor_type"] == VkDescriptorType.COMBINED_IMAGE_SAMPLER.value
)
assert binding["image"]["sampled"] == 1
assert binding["image"]["arrayed"] == 0
assert binding["image"]["ms"] == 0

View File

@ -21,7 +21,7 @@ class SingleLevelFilter(Filter):
return record.levelno == self.passlevel
class VFS_File:
class VFSFile:
def __init__(self, mod_name, vfs_path):
self.mod_name = mod_name
self.vfs_path = vfs_path