Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for variable controlled buildheight #1265

Merged
merged 1 commit into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class FeatureDefinitionContext extends ContextStore<FeatureDefinition> {
private final List<PendingValidation<?>> validations = new ArrayList<>();

public static String parseId(Element node) {
return node.getAttributeValue("id");
return node == null ? null : node.getAttributeValue("id");
}

/** Return the XML element associated with the given feature */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package tc.oc.pgm.filters.matcher.block;

import tc.oc.pgm.api.filter.query.BlockQuery;
import tc.oc.pgm.api.filter.query.Query;
import tc.oc.pgm.filters.matcher.WeakTypedFilter;
import tc.oc.pgm.regions.RegionMatchModule;

/** Matches blocks that are above the max building height */
public class MaxBuildFilter implements WeakTypedFilter<BlockQuery> {

public static final MaxBuildFilter INSTANCE = new MaxBuildFilter();

private MaxBuildFilter() {}

@Override
public Class<? extends BlockQuery> queryType() {
return BlockQuery.class;
}

@Override
public boolean respondsTo(Class<? extends Query> queryType) {
// We can't ever guarantee a response
return false;
}

@Override
public QueryResponse queryTyped(BlockQuery query) {
return query
.moduleOptional(RegionMatchModule.class)
.map(RegionMatchModule::getMaxBuildHeight)
.filter(maxBuild -> query.getBlock().getY() >= maxBuild)
.isPresent()
? QueryResponse.DENY
: QueryResponse.ABSTAIN;
}

@Override
public String toString() {
return "MaxBuildFilter{}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ public AllowFilter(Filter filter) {
super(filter);
}

@Override
public boolean respondsTo(Class<? extends Query> queryType) {
// We can't ever guarantee a response
return false;
}

@Override
public QueryResponse query(Query query) {
switch (filter.query(query)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ public DenyFilter(Filter filter) {
super(filter);
}

@Override
public boolean respondsTo(Class<? extends Query> queryType) {
// We can't ever guarantee a response
return false;
}

@Override
public QueryResponse query(Query query) {
switch (filter.query(query)) {
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/java/tc/oc/pgm/portals/PortalModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public PortalModule parse(MapFactory factory, Logger logger, Document doc)
throws InvalidXMLException {
Set<Portal> portals = Sets.newHashSet();
RegionParser regionParser = factory.getRegions();
RFAContext rfaContext = factory.getModule(RegionModule.class).getRFAContext();
RFAContext.Builder rfaContext = factory.getModule(RegionModule.class).getRFAContextBuilder();

for (Element portalEl : XMLUtils.flattenElements(doc.getRootElement(), "portals", "portal")) {

Expand Down Expand Up @@ -206,7 +206,7 @@ public PortalModule parse(MapFactory factory, Logger logger, Document doc)
*
* <p>The region is extended up by 2m to allow for the height of the player.
*/
private static void protectRegion(RFAContext rfaContext, Region region) {
private static void protectRegion(RFAContext.Builder rfaContext, Region region) {
region =
Union.of(
region,
Expand Down
64 changes: 47 additions & 17 deletions core/src/main/java/tc/oc/pgm/regions/RFAContext.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
package tc.oc.pgm.regions;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class RFAContext {
protected final ArrayListMultimap<RFAScope, RegionFilterApplication> rfas =
ArrayListMultimap.create();
protected final List<RegionFilterApplication> byPriority = new ArrayList<>();
private final Multimap<RFAScope, RegionFilterApplication> rfas;
private final List<RegionFilterApplication> byPriority;

public RFAContext() {}

/** Append the given RFA, giving it the lowest priority */
public void add(RegionFilterApplication rfa) {
this.rfas.put(rfa.scope, rfa);
this.byPriority.add(rfa);
}

/** Prepend the given RFA, giving it the highest priority */
public void prepend(RegionFilterApplication rfa) {
rfa.useRegionPriority = true; // Allows region priority to work on older maps
this.rfas.get(rfa.scope).add(0, rfa);
this.byPriority.add(0, rfa);
public RFAContext(Iterable<RegionFilterApplication> byPriority) {
this.byPriority = ImmutableList.copyOf(byPriority);
ImmutableListMultimap.Builder<RFAScope, RegionFilterApplication> rfaBuilder =
ImmutableListMultimap.builder();
for (RegionFilterApplication rfa : this.byPriority) {
rfaBuilder.put(rfa.scope, rfa);
}
this.rfas = rfaBuilder.build();
}

/** Return all RFAs in the given scope, in priority order */
Expand All @@ -33,4 +30,37 @@ public Iterable<RegionFilterApplication> get(RFAScope scope) {
public Iterable<RegionFilterApplication> getAll() {
return this.byPriority;
}

public static class Builder extends RFAContext {
private final List<RegionFilterApplication> byPriority = new ArrayList<>();

public Builder() {
super(Collections.emptyList());
}

/** Append the given RFA, giving it the lowest priority */
public void add(RegionFilterApplication rfa) {
this.byPriority.add(rfa);
}

/** Prepend the given RFA, giving it the highest priority */
public void prepend(RegionFilterApplication rfa) {
rfa.useRegionPriority = true; // Allows region priority to work on older maps
this.byPriority.add(0, rfa);
}

@Override
public Iterable<RegionFilterApplication> get(RFAScope scope) {
throw new UnsupportedOperationException("Cannot call get without building first!");
}

@Override
public Iterable<RegionFilterApplication> getAll() {
throw new UnsupportedOperationException("Cannot call getAll without building first!");
}

public RFAContext build() {
return new RFAContext(byPriority);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import tc.oc.pgm.api.map.factory.MapFactory;
import tc.oc.pgm.api.region.Region;
import tc.oc.pgm.filters.matcher.StaticFilter;
import tc.oc.pgm.filters.matcher.block.MaxBuildFilter;
import tc.oc.pgm.filters.matcher.party.TeamFilter;
import tc.oc.pgm.filters.operator.DenyFilter;
import tc.oc.pgm.filters.operator.FilterNode;
Expand All @@ -31,10 +32,10 @@ public class RegionFilterApplicationParser {
private final MapFactory factory;
private final FilterParser filterParser;
private final RegionParser regionParser;
private final RFAContext rfaContext;
private final RFAContext.Builder rfaContext;
private final Version proto;

public RegionFilterApplicationParser(MapFactory factory, RFAContext rfaContext) {
public RegionFilterApplicationParser(MapFactory factory, RFAContext.Builder rfaContext) {
this.factory = factory;
this.rfaContext = rfaContext;

Expand Down Expand Up @@ -84,15 +85,18 @@ public void parseLane(Element el) throws InvalidXMLException {
RFAScope.BLOCK_PLACE, new NegativeRegion(region), filter, message, false));
}

public void parseMaxBuildHeight(Element el) throws InvalidXMLException {
final Region region =
new HalfspaceRegion(
new Vector(0, XMLUtils.parseNumber(el, Integer.class), 0), new Vector(0, 1, 0));
final Component message = translatable("match.maxBuildHeight");
public Integer parseMaxBuildHeight(Element el) throws InvalidXMLException {
// Always add the filter, will be no-op as long as the value stays null
prepend(
el,
new RegionFilterApplication(
RFAScope.BLOCK_PLACE,
EverywhereRegion.INSTANCE,
MaxBuildFilter.INSTANCE,
translatable("match.maxBuildHeight"),
false));

for (RFAScope scope : Lists.newArrayList(RFAScope.BLOCK_PLACE)) {
prepend(el, new RegionFilterApplication(scope, region, StaticFilter.DENY, message, false));
}
return el == null ? null : XMLUtils.parseNumber(el, Integer.class);
}

public void parsePlayable(Element el) throws InvalidXMLException {
Expand Down
13 changes: 12 additions & 1 deletion core/src/main/java/tc/oc/pgm/regions/RegionMatchModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,21 @@ public class RegionMatchModule implements MatchModule, Listener {
private final RFAContext rfaContext;
private final boolean useRegionPriority;

public RegionMatchModule(Match match, RFAContext rfaContext) {
private Integer maxBuildHeight;

public RegionMatchModule(Match match, RFAContext rfaContext, Integer maxBuildHeight) {
this.match = match;
this.rfaContext = rfaContext;
this.useRegionPriority = match.getMap().getProto().isNoOlderThan(REGION_PRIORITY_VERSION);
this.maxBuildHeight = maxBuildHeight;
}

public Integer getMaxBuildHeight() {
return maxBuildHeight;
}

public void setMaxBuildHeight(Integer maxBuildHeight) {
this.maxBuildHeight = maxBuildHeight;
}

protected void checkEnterLeave(
Expand Down
26 changes: 17 additions & 9 deletions core/src/main/java/tc/oc/pgm/regions/RegionModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,23 @@
import tc.oc.pgm.util.xml.XMLUtils;

public class RegionModule implements MapModule<RegionMatchModule> {
protected final RFAContext rfaContext;
// Can't be final as this is initially the builder, later replaced by the built copy.
private RFAContext rfaContext;
private final Integer maxBuildHeight;

public RegionModule(RFAContext rfaContext) {
public RegionModule(RFAContext rfaContext, Integer maxBuildHeight) {
this.rfaContext = rfaContext;
this.maxBuildHeight = maxBuildHeight;
}

public RFAContext getRFAContext() {
return rfaContext;
public RFAContext.Builder getRFAContextBuilder() {
if (rfaContext instanceof RFAContext.Builder) return (RFAContext.Builder) rfaContext;
throw new UnsupportedOperationException("Cannot get RFA builder at this stage.");
}

@Override
public RegionMatchModule createMatchModule(Match match) {
return new RegionMatchModule(match, this.rfaContext);
return new RegionMatchModule(match, this.rfaContext, maxBuildHeight);
}

public static class Factory implements MapModuleFactory<RegionModule> {
Expand All @@ -51,7 +55,7 @@ public RegionModule parse(MapFactory factory, Logger logger, Document doc)
}

// parse filter applications
RFAContext rfaContext = new RFAContext();
RFAContext.Builder rfaContext = new RFAContext.Builder();
RegionFilterApplicationParser rfaParser =
new RegionFilterApplicationParser(factory, rfaContext);

Expand Down Expand Up @@ -82,16 +86,20 @@ public RegionModule parse(MapFactory factory, Logger logger, Document doc)
}

// Support <maxbuildheight> syntax
Element heightEl = XMLUtils.getUniqueChild(doc.getRootElement(), "maxbuildheight");
if (heightEl != null) rfaParser.parseMaxBuildHeight(heightEl);
Integer maxBuild =
rfaParser.parseMaxBuildHeight(
XMLUtils.getUniqueChild(doc.getRootElement(), "maxbuildheight"));

return new RegionModule(rfaContext);
return new RegionModule(rfaContext, maxBuild);
}
}

@Override
public void postParse(MapFactory factory, Logger logger, Document doc)
throws InvalidXMLException {

rfaContext = getRFAContextBuilder().build();

for (RegionFilterApplication rfa :
factory.getFeatures().getAll(RegionFilterApplication.class)) {
if (rfa.lendKit && !rfa.kit.isRemovable()) {
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/tc/oc/pgm/tnt/TNTModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public TNTModule parse(MapFactory factory, Logger logger, Document doc)
if (!blockDamage) {
factory
.needModule(RegionModule.class)
.getRFAContext()
.getRFAContextBuilder()
.prepend(
new RegionFilterApplication(
RFAScope.BLOCK_BREAK,
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/java/tc/oc/pgm/variables/VariableParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import tc.oc.pgm.util.xml.XMLUtils;
import tc.oc.pgm.variables.types.BlitzVariable;
import tc.oc.pgm.variables.types.DummyVariable;
import tc.oc.pgm.variables.types.MaxBuildVariable;
import tc.oc.pgm.variables.types.ScoreVariable;
import tc.oc.pgm.variables.types.TeamVariableAdapter;

Expand Down Expand Up @@ -93,4 +94,9 @@ public VariableDefinition<Match> parseTeamAdapter(Element el, String id)
return new VariableDefinition<>(
id, Match.class, var.isDynamic(), vd -> new TeamVariableAdapter(vd, var, team));
}

@MethodParser("maxbuildheight")
public VariableDefinition<Match> parseMaxBuild(Element el, String id) throws InvalidXMLException {
return new VariableDefinition<>(id, Match.class, false, MaxBuildVariable::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package tc.oc.pgm.variables.types;

import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.regions.RegionMatchModule;
import tc.oc.pgm.variables.VariableDefinition;

public class MaxBuildVariable extends AbstractVariable<Match> {

private RegionMatchModule rmm;

public MaxBuildVariable(VariableDefinition<Match> definition) {
super(definition);
}

@Override
public void postLoad(Match match) {
rmm = match.moduleRequire(RegionMatchModule.class);
}

@Override
protected double getValueImpl(Match player) {
Integer val = rmm.getMaxBuildHeight();
return val == null ? -1 : val;
}

@Override
protected void setValueImpl(Match player, double value) {
int val = (int) value;
rmm.setMaxBuildHeight(val <= -1 ? null : val);
}
}