I6 Template Layer

Inform 7 6M62ContentsIntroductionFunction IndexRules Index

Light.i6t

Light contents

Darkness.

"Darkness" is not really a place: but in I6 it has to be an object so that the location-name on the status line can be "Darkness". In I7 we use it as little as possible: note that it has no properties.

13Object thedark "(darkness object)";

Light Measurement.

These two routines, OffersLight and HasLightSource, are largely unchanged from their I6 definitions; see the Inform Designer's Manual, 4th edition, for a commentary. In terms of how they are used in I7, OffersLight is called only by the two rules below; HasLightSource is called also in determining the scope, that is, what is visible to the player.

23[ OffersLight obj j; 24    while (obj) { 25        if (obj has light) rtrue; 26        objectloop (j in obj) if (HasLightSource(j)) rtrue; 27        if ((obj has container) && (obj hasnt open) && (obj hasnt transparent)) rfalse; 28        if ((obj provides component_parent) && (obj.component_parent)) 29            obj = obj.component_parent; 30        else 31            obj = parent(obj); 32    } 33    rfalse; 34]; 35 36[ HasLightSource i j ad sr po; 37    if (i == 0) rfalse; 38    if (i has light) rtrue; 39    if ((IsSeeThrough(i)) && (~~(HidesLightSource(i)))) 40        objectloop (j in i) 41            if (HasLightSource(j)) rtrue; 42    ad = i.&add_to_scope; 43    if (parent(i) ~= 0 && ad ~= 0) { 44        if (metaclass(ad-->0) == Routine) { 45            ats_hls = 0; ats_flag = 1; 46            sr = scope_reason; po = parser_one; 47            scope_reason = LOOPOVERSCOPE_REASON; parser_one = 0; 48            RunRoutines(i, add_to_scope); 49            scope_reason = sr; parser_one = po; 50            ats_flag = 0; if (ats_hls == 1) rtrue; 51        } 52        else { 53            for (j=0 : (WORDSIZE*j)<i.#add_to_scope : j++) 54                if ((ad-->j) && (HasLightSource(ad-->j) == 1)) rtrue; 55        } 56    } 57    if (ComponentHasLight(i)) rtrue; 58    rfalse; 59]; 60 61[ ComponentHasLight o obj next_obj; 62    if (o provides component_child) { 63        obj = o.component_child; 64        while (obj) { 65            next_obj = obj.component_sibling; 66            if (obj has light) rtrue; 67            if (HasLightSource(obj)) rtrue; 68            if ((obj provides component_child) && (ComponentHasLight(obj))) rtrue; 69            obj = next_obj; 70        } 71    } 72    rfalse; 73]; 74 75[ HidesLightSource obj; 76    if (obj == player) rfalse; 77    if (obj has transparent or supporter) rfalse; 78    if (obj has animate) rfalse; 79    if (obj has container) return (obj hasnt open); 80    return (obj hasnt enterable); 81];

Invariant.

The following routines maintain two variables about the light condition of the player:

(a) If on the most recent check the player was in light, then location equals real_location and lightflag is false. (b) If on the most recent check the player was in darkness, then location equals thedark and lightflag is true.

Note that they are not allowed to alter real_location, whose definition has nothing to do with light.

96Global lightflag = false;

Adjust Light Rule.

This rule fires at least once a turn, and more often when the player moves location, since that's likely to invalidate any previous assumptions. It compares the state of light now with the last time it ran, and gives instructions on what to do in each of the four possibilities.

105[ ADJUST_LIGHT_R previous_light_condition; 106    previous_light_condition = lightflag; 107    lightflag = OffersLight(parent(player)); 108 109    if ((previous_light_condition == false) && (lightflag == false)) { 110        location = thedark; 111        rfalse; 112    } 113 114    if ((previous_light_condition == false) && (lightflag == true)) { 115        location = real_location; 116        CarryOutActivity(PRINTING_NEWS_OF_LIGHT_ACT); 117        rfalse; 118    } 119 120    if ((previous_light_condition == true) && (lightflag == false)) { 121        location = thedark; 122        DivideParagraphPoint(); 123        BeginActivity(PRINTING_NEWS_OF_DARKNESS_ACT); 124        if (ForActivity(PRINTING_NEWS_OF_DARKNESS_ACT) == false) { 125            ADJUST_LIGHT_RM('A'); new_line; 126        } 127        EndActivity(PRINTING_NEWS_OF_DARKNESS_ACT); 128        rfalse; 129    } 130 131    if ((previous_light_condition == true) && (lightflag == true)) { 132        location = real_location; 133        rfalse; 134    } 135 136    rfalse; 137];

Silent Light Consideration.

The Adjust Light Rule makes a fuss when light changes: it prints messages, for instance. This rule is silent instead, and simply does the minimum necessary to maintain the light invariant. It is used in only four circumstances:

(a) To determine the initial light condition at start of play. (b) When the player moves from one room to another via the "going" action. (c) When the player moves via PlayerTo, which is used by "now the player is in ...". (d) When the player changes from one persona to another.

Perhaps case (b) is surprising. Why not simply use the adjust light rule, as we do on an instance of "move player to ...", for instance? The answer is that the going action is just about to print details of the new location anyway, so it would be redundant to have the adjust light rule print out details as well.

157[ SilentlyConsiderLight; 158    lightflag = OffersLight(parent(player)); 159    if (lightflag) location = real_location; else location = thedark; 160    rfalse; 161];

Translucency.

IsSeeThrough is used at various places: roughly speaking, it determines whether obj being in scope means that the object-tree contents of obj are in scope.

169[ IsSeeThrough obj; 170    if ((obj has supporter) 171        || (obj has transparent) 172        || (obj has animate) 173        || ((obj has container) && (obj has open))) 174        rtrue; 175   rfalse; 176];

Visibility Parent.

The idea of VisibilityParent is that it takes us from a given position in the object tree, o, to the next visible position above. Note that (1) A container has an inside and an outside: this routine calculates from the "inside of o", which is why it returns nothing from an opaque closed container; (2) Component parts are (for purposes of this routine) attached to the outside surface of a container, so that if o is part of a closed opaque container then the visibility parent of o is its actual parent.

189[ VisibilityParent o; 190    if (o && (o has container) && (o hasnt open) && (o hasnt transparent)) return nothing; 191    if (o) o = CoreOfParentOfCoreOf(o); 192    return o; 193];

Find Visibility Levels.

The following routine sets the pair of variables visibility_ceiling, the highest visible point in the object tree above the player – or thedark if the player cannot see at all – and visibility_levels, the number of steps of VisibilityParent needed to reach this ceiling; or 0 if the player cannot see at all.

203[ FindVisibilityLevels lc up; 204    if (location == thedark) { 205        visibility_ceiling = thedark; 206        visibility_levels = 0; 207    } else { 208        visibility_ceiling = player; 209        while (true) { 210            up = VisibilityParent(visibility_ceiling); 211            if (up == 0) break; 212            visibility_ceiling = up; 213            lc++; 214        } 215        visibility_levels = lc; 216    } 217];

Touchability Ceiling.

Analogously:

223[ TouchabilityCeiling original o p; 224    o = original; 225    while (o) { 226        p = CoreOfParentOfCoreOf(o); 227        if (p ofclass K1_room) return p; 228        if (p == nothing) return o; 229        if ((FollowRulebook(REACHING_OUTSIDE_RB, p)) && (RulebookFailed())) 230            return p; 231        o = p; 232    } 233    return o; 234];

Scope Ceiling.

Scope is almost the same thing as visibility, but not quite, and the following routine does not quite duplicate the calculation of FindVisibilityLevels. The difference arises in the first step, where we take the parent of pos, not the core of the parent of the core of pos: this makes a difference if pos is inside a container which is itself part of something else.

245[ ScopeCeiling pos c; 246    if (pos == player && location == thedark) return thedark; 247    c = parent(pos); 248    if (c == 0) return pos; 249    while (VisibilityParent(c)) c = VisibilityParent(c); 250    return c; 251];

Object Is Untouchable.

The following routine imitates the I6 library one of the same name, but works instead by delegating the decision to the accessibility rulebook.

It is not easy to answer the question of whether someone other than the player, but in another room, can touch a multiply-present object (a two-sided door or a backdrop) and we err on the side of caution by saying yes in such cases. The question would only be asked in the case of a "try" action deliberately caused by the author, or by an instruction made by the player to someone not in the same room: in the first case, we will assume that the author knows what he is doing, and in the second case, the circumstances in which saying yes would be a wrong call are highly improbable.

268[ ObjectIsUntouchable item silent_flag p save_sp decision moving x; 269    if (LocationOf(p) ~= real_location) { 270        for (x = CoreOf(item): x: x = CoreOfParentOfCoreOf(x)) { 271            if (x ofclass K4_door or K7_backdrop) { 272                moving = true; 273                MoveFloatingObjects(LocationOf(p)); 274                break; 275            } 276        } 277    } 278    untouchable_object = item; untouchable_silence = silent_flag; 279    touch_persona = p; if (p == actor) touch_persona = 0; 280    save_sp = say__p; say__p = 0; 281    @push actor; actor = p; 282    if (FollowRulebook(ACCESSIBILITY_RB, 0, true)) { 283        if (RulebookSucceeded()) decision = false; 284        else decision = true; 285    } else decision = false; 286    @pull actor; 287    if (say__p == false) say__p = save_sp; 288    if (moving) MoveFloatingObjects(); 289    untouchable_silence = 0; 290    return decision; 291];

Access Through Barriers Rule.

296[ ACCESS_THROUGH_BARRIERS_R ancestor i j external p; 297    p = touch_persona; if (p == 0) p = actor; 298 299    ancestor = CommonAncestor(p, untouchable_object); 300    if ((ancestor == 0) && (LocationOf(untouchable_object) == nothing) 301        && ((untouchable_object ofclass K4_door or K7_backdrop) == false)) { 302        if (touch_persona == 0) { 303            if ((actor == player) && (untouchable_silence == false)) { 304                ACCESS_THROUGH_BARRIERS_RM('A', untouchable_object); 305                new_line; 306            } 307        } 308        RulebookFails(); 309        rtrue; 310    } 311 312    ! First, a barrier between the player and the ancestor. 313 314    if (CoreOf(p) ~= ancestor) { 315        i = parent(CoreOf(p)); j = CoreOf(i); external = false; 316        if (j ~= i) { i = j; external = true; } 317        while (i~=ancestor && i) { 318            if ((external == false) 319                && (FollowRulebook(REACHING_OUTSIDE_RB, i)) 320                && (RulebookFailed())) rtrue; ! Barrier 321            i = parent(CoreOf(i)); external = false; 322            if (~~(i ofclass K5_container)) { 323                j = CoreOf(i); 324                if (j ~= i) { i = j; external = true; } 325            } 326        } 327    } 328 329    ! Second, a barrier between the item and the ancestor. 330 331    if (CoreOf(untouchable_object) ~= ancestor) { 332        ! We can always get to the core of the item. 333        i = CoreOf(untouchable_object); 334        ! This will be on the inside of its parent, if its parent is a 335        ! container, so there should be no exemption. 336        i = parent(i); external = false; 337        while (i~=ancestor && i) { 338            if ((external == false) && 339                (FollowRulebook(REACHING_INSIDE_RB, i)) && 340                (RulebookFailed())) rtrue; ! Barrier 341            i = CoreOf(i); 342            if (i == ancestor) break; 343            i = parent(i); external = false; 344            if (~~(i ofclass K5_container)) { 345                j = CoreOf(i); 346                if (j ~= i) { i = j; external = true; } 347            } 348        } 349    } 350 351    RulebookSucceeds(); ! No barrier 352    rtrue; 353];

Can't Reach Inside Closed Containers Rule.

358[ CANT_REACH_INSIDE_CLOSED_R; 359    if (parameter_value has container && parameter_value hasnt open) { 360        if (touch_persona == 0) { 361            if ((actor == player) && (untouchable_silence == false)) { 362                CANT_REACH_INSIDE_CLOSED_RM('A', parameter_value); 363                new_line; 364            } 365        } 366        RulebookFails(); rtrue; 367    } 368    rfalse; 369];

Can't Reach Outside Closed Containers Rule.

374[ CANT_REACH_OUTSIDE_CLOSED_R; 375    if (parameter_value has container && parameter_value hasnt open) { 376        if (touch_persona == 0) { 377            if ((actor == player) && (untouchable_silence == false)) { 378                CANT_REACH_OUTSIDE_CLOSED_RM('A', parameter_value); 379                new_line; 380            } 381        } 382        RulebookFails(); rtrue; 383    } 384    rfalse; 385];

Can't Reach Inside Rooms Rule.

390[ CANT_REACH_INSIDE_ROOMS_R; 391    if (parameter_value && parameter_value ofclass K1_room) { 392        if (touch_persona == 0) { 393            if ((actor == player) && (untouchable_silence == false)) { 394                CANT_REACH_INSIDE_ROOMS_RM('A', parameter_value); 395                new_line; 396            } 397        } 398        RulebookFails(); rtrue; 399    } 400    rfalse; 401]; 402